diff --git a/contributing/code/conventions.rst b/contributing/code/conventions.rst index 025baeb0c4a..067906c8796 100644 --- a/contributing/code/conventions.rst +++ b/contributing/code/conventions.rst @@ -29,7 +29,7 @@ When an object has a "main" many relation with related "things" The usage of these methods are only allowed when it is clear that there is a main relation: -* a ``CookieJar`` has many ``Cookie``s; +* a ``CookieJar`` has many ``Cookie`` objects; * a Service ``Container`` has many services and many parameters (as services is the main relation, we use the naming convention for this relation); diff --git a/contributing/code/patches.rst b/contributing/code/patches.rst index db55a516e78..636ec617d1e 100644 --- a/contributing/code/patches.rst +++ b/contributing/code/patches.rst @@ -124,7 +124,7 @@ Check that all tests still pass and push your branch remotely: You can now discuss your patch on the `dev mailing-list`_ or make a pull request (they must be done on the ``fabpot/symfony`` repository). -If you are going to send and email to the mailing-list, don't forget to +If you are going to send an email to the mailing-list, don't forget to reference you branch URL (``http://github.com/USERNAME/symfony.git BRANCH_NAME``). diff --git a/glossary.rst b/glossary.rst index 0ceb697970f..7599dd9771a 100644 --- a/glossary.rst +++ b/glossary.rst @@ -23,4 +23,20 @@ Glossary A *Front Controller* is a short PHP that lives in the web directory of your project. Typically, *all* requests are handled by executing the same front controller, whose job is to bootstrap the Symfony - application. \ No newline at end of file + application. + + Service + A *Service* is a generic term for any PHP object that performs a + specific task. In Symfony2, services are often configured and retrieved + from the service container. An application that has many decoupled + services is said to follow a `service-oriented architecture`_ + + Service Container + A *Service Container*, also known as a *Dependency Injection Container*, + is a special object that manages the instantiation of services inside + an application. Instead of creating services directly, the developer + *trains* the service container (via configuration) on how to create + the services. The service container takes care of lazily instantiating + and injecting dependent services. + +.. _`service-oriented architecture`: http://wikipedia.org/wiki/Service-oriented_architecture diff --git a/guides/forms/fields.rst b/guides/forms/fields.rst index e3a3dd0e74c..370136c214c 100644 --- a/guides/forms/fields.rst +++ b/guides/forms/fields.rst @@ -37,7 +37,7 @@ this initial value, you can set it in the ``data`` option. When you set the ``data`` option, the field will also not write the the domain object, because the ``property_path`` option will implicitely be - ``null``. Read ```property_path```_ for more information. + ``null``. Read :ref:`form-field-property_path` for more information. ``required`` ~~~~~~~~~~~~ @@ -92,6 +92,8 @@ set the ``trim`` option to ``false``. assert(' Data ' === $field->getData()); +.. _form-field-property_path: + ``property_path`` ~~~~~~~~~~~~~~~~~ @@ -212,26 +214,5 @@ Symfony2 ships with the following fields: fields/index -* :doc:`BirthdayField ` -* :doc:`CheckboxField ` -* :doc:`ChoiceField ` -* :doc:`CollectionField ` -* :doc:`CountryField ` -* :doc:`DateField ` -* :doc:`DateTimeField ` -* :doc:`EntityChoiceField ` -* :doc:`FileField ` -* :doc:`HiddenField ` -* :doc:`IntegerField ` -* :doc:`LanguageField ` -* :doc:`LocaleField ` -* :doc:`MoneyField ` -* :doc:`NumberField ` -* :doc:`PasswordField ` -* :doc:`PercentField ` -* :doc:`RepeatedField ` -* :doc:`TextareaField ` -* :doc:`TextField ` -* :doc:`TimeField ` -* :doc:`TimezoneField ` -* :doc:`UrlField ` +.. include:: fields/map.rst.inc + diff --git a/guides/forms/fields/BirthdayField.rst b/guides/forms/fields/BirthdayField.rst new file mode 100644 index 00000000000..c2c446b3b36 --- /dev/null +++ b/guides/forms/fields/BirthdayField.rst @@ -0,0 +1,5 @@ +BirthdayField +============= + +Documentation for the :class:`Symfony\\Component\\Form\\BirthdayField` +class is currently only available via the generated API. \ No newline at end of file diff --git a/guides/forms/fields/CheckboxField.rst b/guides/forms/fields/CheckboxField.rst new file mode 100644 index 00000000000..31ef80ecb92 --- /dev/null +++ b/guides/forms/fields/CheckboxField.rst @@ -0,0 +1,5 @@ +CheckboxField +============= + +Documentation for the :class:`Symfony\\Component\\Form\\CheckboxField` +class is currently only available via the generated API. \ No newline at end of file diff --git a/guides/forms/fields/ChoiceField.rst b/guides/forms/fields/ChoiceField.rst new file mode 100644 index 00000000000..077562024f2 --- /dev/null +++ b/guides/forms/fields/ChoiceField.rst @@ -0,0 +1,5 @@ +ChoiceField +=========== + +Documentation for the :class:`Symfony\\Component\\Form\\ChoiceField` +class is currently only available via the generated API. \ No newline at end of file diff --git a/guides/forms/fields/CountryField.rst b/guides/forms/fields/CountryField.rst new file mode 100644 index 00000000000..f868b4b4154 --- /dev/null +++ b/guides/forms/fields/CountryField.rst @@ -0,0 +1,5 @@ +CountryField +============ + +Documentation for the :class:`Symfony\\Component\\Form\\CountryField` +class is currently only available via the generated API. \ No newline at end of file diff --git a/guides/forms/fields/DateField.rst b/guides/forms/fields/DateField.rst new file mode 100644 index 00000000000..56480dc48fa --- /dev/null +++ b/guides/forms/fields/DateField.rst @@ -0,0 +1,5 @@ +DateField +========= + +Documentation for the :class:`Symfony\\Component\\Form\\DateField` +class is currently only available via the generated API. \ No newline at end of file diff --git a/guides/forms/fields/DateTimeField.rst b/guides/forms/fields/DateTimeField.rst new file mode 100644 index 00000000000..6541616d73f --- /dev/null +++ b/guides/forms/fields/DateTimeField.rst @@ -0,0 +1,5 @@ +DateTimeField +============= + +Documentation for the :class:`Symfony\\Component\\Form\\DateTimeField` +class is currently only available via the generated API. \ No newline at end of file diff --git a/guides/forms/fields/EntityChoiceField.rst b/guides/forms/fields/EntityChoiceField.rst new file mode 100644 index 00000000000..6cfcd3029cd --- /dev/null +++ b/guides/forms/fields/EntityChoiceField.rst @@ -0,0 +1,5 @@ +EntityChoiceField +================= + +Documentation for the :class:`Symfony\\Component\\Form\\EntityChoiceField` +class is currently only available via the generated API. \ No newline at end of file diff --git a/guides/forms/fields/FileField.rst b/guides/forms/fields/FileField.rst new file mode 100644 index 00000000000..88e7ac47a6c --- /dev/null +++ b/guides/forms/fields/FileField.rst @@ -0,0 +1,5 @@ +FileField +========= + +Documentation for the :class:`Symfony\\Component\\Form\\FileField` +class is currently only available via the generated API. \ No newline at end of file diff --git a/guides/forms/fields/HiddenField.rst b/guides/forms/fields/HiddenField.rst new file mode 100644 index 00000000000..fe99815759c --- /dev/null +++ b/guides/forms/fields/HiddenField.rst @@ -0,0 +1,5 @@ +HiddenField +=========== + +Documentation for the :class:`Symfony\\Component\\Form\\HiddenField` +class is currently only available via the generated API. \ No newline at end of file diff --git a/guides/forms/fields/IntegerField.rst b/guides/forms/fields/IntegerField.rst new file mode 100644 index 00000000000..330e233fab1 --- /dev/null +++ b/guides/forms/fields/IntegerField.rst @@ -0,0 +1,5 @@ +IntegerField +============ + +Documentation for the :class:`Symfony\\Component\\Form\\IntegerField` +class is currently only available via the generated API. \ No newline at end of file diff --git a/guides/forms/fields/LanguageField.rst b/guides/forms/fields/LanguageField.rst new file mode 100644 index 00000000000..9177c5de156 --- /dev/null +++ b/guides/forms/fields/LanguageField.rst @@ -0,0 +1,5 @@ +LanguageField +============= + +Documentation for the :class:`Symfony\\Component\\Form\\LanguageField` +class is currently only available via the generated API. \ No newline at end of file diff --git a/guides/forms/fields/LocaleField.rst b/guides/forms/fields/LocaleField.rst new file mode 100644 index 00000000000..e30f31a8132 --- /dev/null +++ b/guides/forms/fields/LocaleField.rst @@ -0,0 +1,5 @@ +LocaleField +=========== + +Documentation for the :class:`Symfony\\Component\\Form\\LocaleField` +class is currently only available via the generated API. \ No newline at end of file diff --git a/guides/forms/fields/MoneyField.rst b/guides/forms/fields/MoneyField.rst new file mode 100644 index 00000000000..cf128174bfb --- /dev/null +++ b/guides/forms/fields/MoneyField.rst @@ -0,0 +1,5 @@ +MoneyField +========== + +Documentation for the :class:`Symfony\\Component\\Form\\MoneyField` +class is currently only available via the generated API. \ No newline at end of file diff --git a/guides/forms/fields/NumberField.rst b/guides/forms/fields/NumberField.rst new file mode 100644 index 00000000000..484034aae91 --- /dev/null +++ b/guides/forms/fields/NumberField.rst @@ -0,0 +1,5 @@ +NumberField +=========== + +Documentation for the :class:`Symfony\\Component\\Form\\NumberField` +class is currently only available via the generated API. \ No newline at end of file diff --git a/guides/forms/fields/PasswordField.rst b/guides/forms/fields/PasswordField.rst new file mode 100644 index 00000000000..11329bfd3dd --- /dev/null +++ b/guides/forms/fields/PasswordField.rst @@ -0,0 +1,5 @@ +PasswordField +============= + +Documentation for the :class:`Symfony\\Component\\Form\\PasswordField` +class is currently only available via the generated API. \ No newline at end of file diff --git a/guides/forms/fields/PercentField.rst b/guides/forms/fields/PercentField.rst new file mode 100644 index 00000000000..725323c838f --- /dev/null +++ b/guides/forms/fields/PercentField.rst @@ -0,0 +1,5 @@ +PercentField +============ + +Documentation for the :class:`Symfony\\Component\\Form\\PercentField` +class is currently only available via the generated API. \ No newline at end of file diff --git a/guides/forms/fields/TextField.rst b/guides/forms/fields/TextField.rst new file mode 100644 index 00000000000..d04b6af92dd --- /dev/null +++ b/guides/forms/fields/TextField.rst @@ -0,0 +1,5 @@ +TextField +========= + +Documentation for the :class:`Symfony\\Component\\Form\\TextField` +class is currently only available via the generated API. \ No newline at end of file diff --git a/guides/forms/fields/TextareaField.rst b/guides/forms/fields/TextareaField.rst new file mode 100644 index 00000000000..748418b626d --- /dev/null +++ b/guides/forms/fields/TextareaField.rst @@ -0,0 +1,5 @@ +TextareaField +============= + +Documentation for the :class:`Symfony\\Component\\Form\\TextareaField` +class is currently only available via the generated API. \ No newline at end of file diff --git a/guides/forms/fields/TimeField.rst b/guides/forms/fields/TimeField.rst new file mode 100644 index 00000000000..ac277c9ca60 --- /dev/null +++ b/guides/forms/fields/TimeField.rst @@ -0,0 +1,5 @@ +TimeField +========= + +Documentation for the :class:`Symfony\\Component\\Form\\TimeField` +class is currently only available via the generated API. \ No newline at end of file diff --git a/guides/forms/fields/TimezoneField.rst b/guides/forms/fields/TimezoneField.rst new file mode 100644 index 00000000000..5d4c550c62f --- /dev/null +++ b/guides/forms/fields/TimezoneField.rst @@ -0,0 +1,5 @@ +TimezoneField +============= + +Documentation for the :class:`Symfony\\Component\\Form\\TimezoneField` +class is currently only available via the generated API. \ No newline at end of file diff --git a/guides/forms/fields/UrlField.rst b/guides/forms/fields/UrlField.rst new file mode 100644 index 00000000000..7f8c2041a89 --- /dev/null +++ b/guides/forms/fields/UrlField.rst @@ -0,0 +1,5 @@ +UrlField +======== + +Documentation for the :class:`Symfony\\Component\\Form\\UrlField` +class is currently only available via the generated API. \ No newline at end of file diff --git a/guides/forms/fields/index.rst b/guides/forms/fields/index.rst new file mode 100644 index 00000000000..e72a473c036 --- /dev/null +++ b/guides/forms/fields/index.rst @@ -0,0 +1,34 @@ +Built-in Fields +=============== + +Symfony2 ships with the following fields: + +.. toctree:: + :maxdepth: 2 + :hidden: + + BirthdayField + CheckboxField + ChoiceField + CollectionField + CountryField + DateField + DateTimeField + EntityChoiceField + FileField + HiddenField + IntegerField + LanguageField + LocaleField + MoneyField + NumberField + PasswordField + PercentField + RepeatedField + TextareaField + TextField + TimeField + TimezoneField + UrlField + +.. include:: map.rst.inc diff --git a/guides/forms/fields/map.rst.inc b/guides/forms/fields/map.rst.inc new file mode 100644 index 00000000000..d793757160e --- /dev/null +++ b/guides/forms/fields/map.rst.inc @@ -0,0 +1,23 @@ +* :doc:`BirthdayField ` +* :doc:`CheckboxField ` +* :doc:`ChoiceField ` +* :doc:`CollectionField ` +* :doc:`CountryField ` +* :doc:`DateField ` +* :doc:`DateTimeField ` +* :doc:`EntityChoiceField ` +* :doc:`FileField ` +* :doc:`HiddenField ` +* :doc:`IntegerField ` +* :doc:`LanguageField ` +* :doc:`LocaleField ` +* :doc:`MoneyField ` +* :doc:`NumberField ` +* :doc:`PasswordField ` +* :doc:`PercentField ` +* :doc:`RepeatedField ` +* :doc:`TextareaField ` +* :doc:`TextField ` +* :doc:`TimeField ` +* :doc:`TimezoneField ` +* :doc:`UrlField ` diff --git a/guides/index.rst b/guides/index.rst index b398d1a9767..9f69c20d385 100644 --- a/guides/index.rst +++ b/guides/index.rst @@ -18,6 +18,7 @@ Dive into Symfony2 with the topical guides: event/index tools/index bundles/index + service_container/index internals/index symfony1 stable_api diff --git a/guides/internals/overview.rst b/guides/internals/overview.rst index 0d94b9edcd1..f087190a878 100644 --- a/guides/internals/overview.rst +++ b/guides/internals/overview.rst @@ -62,7 +62,7 @@ Dependency Injection component and a powerful plugin system (bundles). .. seealso:: Read more about the :doc:`HttpKernel ` component. Read more about - :doc:`Dependency Injection ` and + :doc:`Dependency Injection ` and :doc:`Bundles `. ``FrameworkBundle`` Bundle diff --git a/guides/map.rst.inc b/guides/map.rst.inc index e6879a6938e..7eb0f6cc944 100644 --- a/guides/map.rst.inc +++ b/guides/map.rst.inc @@ -28,7 +28,8 @@ * **Forms**: * :doc:`Overview ` | - * :doc:`Templates ` + * :doc:`Templates ` | + * :doc:`Form Fields ` * **Security**: @@ -63,9 +64,10 @@ * :doc:`Best Practices ` | * :doc:`Configuration ` -* **Dependency Injection**: +* **Service Container**: - * :doc:`Extensions ` + * :doc:`Service Container ` + * :doc:`Extensions ` * **Internals**: diff --git a/guides/security/authorization.rst b/guides/security/authorization.rst index 6c9f6980dc7..16cd86765a9 100644 --- a/guides/security/authorization.rst +++ b/guides/security/authorization.rst @@ -81,12 +81,15 @@ Access control rules can match a request in many different ways: - { path: /admin/.*, role: ROLE_ADMIN } # match the controller class name - - { controller: .*\\.*Bundle\\Admin\\.*, role: ROLE_ADMIN } + - + attributes: + _controller: '.*Bundle\\\.*\\\Admin.*' + role: ROLE_ADMIN # match any request attribute - attributes: - - { key: _controller, pattern: .*\\.*Bundle\\Admin\\.* } + - { key: _controller, pattern: '.*Bundle\\\.*\\\Admin.*' } role: ROLE_ADMIN .. code-block:: xml @@ -98,11 +101,13 @@ Access control rules can match a request in many different ways: - + + + - + @@ -116,12 +121,15 @@ Access control rules can match a request in many different ways: array('path' => '/admin/.*', 'role' => 'ROLE_ADMIN'), // match the controller class name - array('controller' => '.*\\.*Bundle\\Admin\\.*', 'role' => 'ROLE_ADMIN'), + array( + 'attributes' => array('controller' => '.*Bundle\\\.*\\\Admin.*'), + 'role' => 'ROLE_ADMIN' + ), // match any request attribute array( 'attributes' => array( - array('key' => '_controller', 'pattern' => '.*\\.*Bundle\\Admin\\.*'), + array('key' => '_controller', 'pattern' => '.*Bundle\\\.*\\\Admin.*'), ), 'role' => 'ROLE_ADMIN', ), diff --git a/guides/service_container/index.rst b/guides/service_container/index.rst new file mode 100644 index 00000000000..169e94a499a --- /dev/null +++ b/guides/service_container/index.rst @@ -0,0 +1,724 @@ +.. index:: + single: Service Container + single: Dependency Injection Container + +The Service Container +===================== + +A modern PHP application is full of objects. One object may facilitate the +delivery of email messages while another may allow you to persist information +into a database. In your application, you may create an object that manages +your product inventory, or another object that processes data from a third-party +API. The point is that a modern application does many things and is organized +into many objects that handle each task. + +In this guide, we'll talk about a special PHP object in Symfony2 that helps +your instantiate, organize and retrieve the many objects of your application. +This object, called a service container, will allow you to standardize and +centralize the way objects are constructed in your application. The container +makes your life easier, is super fast, and emphasizes an architecture that +promotes reusable and decoupled code. And since all core Symfony2 classes +use the container, you'll learn how to extend, configure and use any object +in Symfony2. In large part, the service container is the biggest contributor +to the speed and extensibility of Symfony2. + +Finally, configuring and using the service container is easy. By the end +of this chapter, you'll be comfortable creating your own objects via the +container and customizing objects from any third-party bundle. You'll begin +writing code that is more reusable, testable and decoupled, simply because +the service container makes writing good code so easy. + +.. index:: + single: Service Container; What is a service? + +What is a Service? +------------------ + +Put simply, a :term:`Service` is any PHP object that does something. It's +a purposefully-generic name used in computer science to describe an object +that's created for a specific purpose (e.g. delivering emails). You don't +have to do anything special to make a service: simply write a PHP class +with some code that accomplishes a specific task. Congratulations, you've +just created a service! + +So what's the big deal then? The advantage of thinking about "services" is +that you begin to think about separating each piece of functionality in your +application into a series of services. Since each service does just one job, +you can easily access each service and use its functionality wherever you +need it. Each service can also be more easily tested and configured since +it's separated from the other functionality in your application. This idea +is called `service-oriented architecture`_ and is not unique to Symfony2 +or even PHP. Structuring your application around a set of independent service +classes is a well-known and trusted object-oriented best-practice. These skills +are key to being a good developer in almost any language. + +.. index:: + single: Service Container; What is? + +What is a Service Container? +---------------------------- + +A :term:`Service Container` (or *dependency injection container*) is simply +a PHP object that manages the instantiation of services (i.e. objects). +For example, suppose we have a simple PHP class that delivers email messages. +Without a service container, we must manually create the object whenever +we need it: + +.. code-block:: php + + use Sensio\HelloBundle\Mailer; + + $mailer = new Mailer('sendmail'); + $mailer->send('ryan@foobar.net', ... ); + +This is easy enough. The imaginary ``Mailer`` class allows us to configure +the method used to deliver the email messages (e.g. ``sendmail``, ``smtp``, etc). +But what if we wanted to use the mailer service somewhere else? We certainly +don't want to repeat the mailer configuration *every* time we need to use +the ``Mailer`` object. What if we needed to change the ``transport`` from +``sendmail`` to ``smtp`` everywhere in the application? We'd need to hunt +down every place we create a ``Mailer`` service and change it. + +.. index:: + single: Service Container; Configuring services + +Creating/Configuring Services in the Container +---------------------------------------------- + +A better answer is to let the service container create the ``Mailer`` object +for you. In order for this to work, we must *teach* the container how to +create the ``Mailer`` service. This is done via configuration, which can +be specified in YAML, XML or PHP: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/config.yml + services: + my_mailer: + class: Sensio\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( + 'Sensio\HelloBundle\Mailer', + array('sendmail') + )); + +.. note:: + + When Symfony2 initializes, it builds the service container using the + application configuration (``app/config/config.yml`` by default). The + exact file that's loaded is dictated by the ``AppKernel::loadConfig()`` + method, which loads an environment-specific configuration file (e.g. + ``config_dev.yml`` for the ``dev`` environment or ``config_prod.yml`` + for ``prod``). + +An instance of the ``Sensio\HelloBundle\Mailer`` object is now available via +the service container. Since the container is available in any traditional +Symfony2 controller, we can easily access the new ``my_mailer`` service:: + + class HelloController extends Controller + { + // ... + + public function sendEmailAction() + { + // ... + $mailer = $this->container->get('my_mailer'); + $mailer->send('ryan@foobar.net', ... ); + } + } + +.. tip:: + + When using a traditional controller, there's an even shorter way to + access a service from the container. This is exactly equivalent to the + above method, but with less keystrokes:: + + $mailer = $this->get('my_mailer'); + +When we ask for the ``my_mailer`` service from the container, the container +constructs the object and returns it. This is another major advantage of +using the service container. Namely, a service is *never* constructed until +it's needed. If you define a service and never use it on a request, the service +is never created. This saves memory and increases the speed of your application. +This also means that there's very little or no performance hit for defining +lot's of services. Services that are never used are never constructed. + +As an added bonus, the ``Mailer`` service is only created once and the same +instance is returned each time you ask for the service. This is almost always +the behavior you'll need (it's more flexible and performant), but we'll learn +later how you can configure a service that has multiple instances. + +Service Parameters +------------------ + +The creation of new services (i.e. objects) via the container is pretty +straightforward. Parameters make defining services more organized and flexible: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/config.yml + parameters: + my_mailer.class: Sensio\HelloBundle\Mailer + my_mailer.transport: sendmail + + services: + my_mailer: + class: %my_mailer.class% + arguments: [%my_mailer.transport%] + + .. code-block:: xml + + + + Sensio\HelloBundle\Mailer + sendmail + + + + + %my_mailer.transport% + + + + .. code-block:: php + + // app/config/config.php + use Symfony\Component\DependencyInjection\Definition; + + $container->setParameter('my_mailer.class', 'Sensio\HelloBundle\Mailer'); + $container->setParameter('my_mailer.transport', 'sendmail'); + + $container->setDefinition('my_mailer', new Definition( + '%my_mailer.class%', + array('%my_mailer.transport%') + )); + +The end result is exactly the same as before - the difference is only in +*how* we defined the service. By surrounding the ``my_mailer.class`` and +``my_mailer.transport`` strings in percent (``%``) signs, the container knows +to look for parameters with those names. When the container is built, it +looks up the value of each parameter and uses it in the service definition. + +The purpose of parameters is to feed information into services. Of course +there was nothing wrong with defining the service without using any parameters. +Parameters, however, have several advantages: + +* separation and organization of all of the service "options" under a single + ``parameters`` key; + +* parameter values can be used in multiple service definitions; + +* when creating a service in a bundle (we'll show this shortly), using parameters + allows the service to be easily customized in your application. + +The choice of using or not using parameters is up to you. High-quality +third-party bundles will *always* use parameters as they make the service +stored in the container more configurable. For the services in your application, +however, you may not need the flexibility of parameters. + +Importing other Container Configuration Resources +------------------------------------------------- + +.. tip:: + + In this section, we'll refer to service configuration files as *resources*. + This is to highlight that fact that, while most configuration resources + will be files (e.g. YAML, XML, PHP), Symfony2 is so flexible that configuration + could be loaded from anywhere (e.g. a database or even via an external + web service). + +The service container is built using a single configuration resource +(``app/config/config.yml`` by default). All other service configuration( +(including the core Symfony2 and third-party bundle configuration) must +be imported from inside this file in one way or another. This gives you absolute +flexibility over the services in your application. + +External service configuration can be imported in two different ways. First, +we'll talk about the method that you'll use most commonly in your application +- the ``imports`` directive. In the following section, we'll introduce the +second method, which is the flexible and preferred method for importing service +configuration from third-party bundles. + +.. index:: + single: Service Container; imports + +Importing Configuration with ``imports`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +So far, we've placed our ``my_mailer`` service container definition directly +in the application configuration file (e.g. ``app/config/config.yml``). Of +course, since the ``Mailer`` class itself lives inside ``HelloBundle``, it +makes more sense to put the ``my_mailer`` container definition inside the +bundle as well. + +First, move the ``my_mailer`` container definition into a new container resource +file in ``HelloBundle``. If the ``Resources`` or ``Resources/config`` directories +don't exist, create them. + +.. configuration-block:: + + .. code-block:: yaml + + # src/Sensio/HelloBundle/Resources/config/services.yml + parameters: + my_mailer.class: Sensio\HelloBundle\Mailer + my_mailer.transport: sendmail + + services: + my_mailer: + class: %my_mailer.class% + arguments: [%my_mailer.transport%] + + .. code-block:: xml + + + + Sensio\HelloBundle\Mailer + sendmail + + + + + %my_mailer.transport% + + + + .. code-block:: php + + // src/Sensio/HelloBundle/Resources/config/services.php + use Symfony\Component\DependencyInjection\Definition; + + $container->setParameter('my_mailer.class', 'Sensio\HelloBundle\Mailer'); + $container->setParameter('my_mailer.transport', 'sendmail'); + + $container->setDefinition('my_mailer', new Definition( + '%my_mailer.class%', + array('%my_mailer.transport%') + )); + +The definition itself hasn't changed, only its location. Of course the service +container doesn't know about the new resource file. Fortunately, we can +easily import the resource file using the ``imports`` key in the application +configuration. + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/config.yml + imports: + hello_bundle: + resource: @HelloBundle/Resources/config/services.yml + + .. code-block:: xml + + + + + + + .. code-block:: php + + // app/config/config.php + $this->import('@HelloBundle/Resources/config/services.php'); + +The ``imports`` directive allows your application to include service container +configuration resources from any other location (most commonly from bundles). +The ``resource`` location, for files, is the absolute path to the resource +file. The special ``@HelloBundle`` syntax resolves to the directory path to +the ``HelloBundle``. This helps you specify the path to the resource without +worrying later if you move the ``HelloBundle`` to a different directory. + +.. index:: + single: Service Container; Extension configuration + +.. _service-container-extension-configuration: + +Importing Configuration via Container Extensions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When developing in Symfony2, you'll most commonly use the ``imports`` directive +to import container configuration from the bundles you've created specifically +for your application. Third-party bundle container configuration (including +the Symfony2 core services), are usually loaded using another method that's +more flexible and easy to configure in your application. + +Here's how it works. Internally, each bundle defines its services very much +like we've seen so far in this guide. Namely, a bundle uses one or more +configuration resource files (usually XML) to specify the parameters and +services for that bundle. However, instead of importing each of these resources +directly from your application configuration using the ``imports`` directive, +you can simply invoke a *service container extension* inside the bundle that +does all the work for you. A service container extension is a PHP class created +by the bundle author to take care of all the service container configuration +on your behalf. + +Take the ``FrameworkBundle`` - the core Symfony2 framework bundle - as an +example. The presence of the following code in your application configuration +invokes the service container extension inside the ``FrameworkBundle``: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/config.yml + app.config: ~ + + .. code-block:: xml + + + + + .. code-block:: php + + // app/config/config.php + $container->loadFromExtension('app', 'config'); + +When the configuration is parsed, the container looks for an extension that +can handle the ``app.config`` configuration directive. The extension in question, +which lives in the ``FrameworkBundle``, is invoked and the service configuration +for the ``FrameworkBundle`` is loaded. If you remove the ``app.config`` key +from your application configuration file, the core Symfony2 services won't +be loaded. The point is that you're in control: the Symfony2 framework doesn't +contain any magic or perform any actions that you don't have control over. + +Of course you can do much more than simply "activate" the service container +extension of the ``FrameworkBundle``. Each extension allows you to easily +customize the bundle, without worrying about how the internal services are +defined. In fact, the default configuration beneath ``app.config`` looks much +more like this: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/config.yml + app.config: + charset: UTF-8 + error_handler: null + csrf_protection: + enabled: true + secret: xxxxxxxxxx + router: { resource: "%kernel.root_dir%/config/routing.yml" } + # ... + + .. code-block:: xml + + + + + + + + + .. code-block:: php + + // app/config/config.php + $container->loadFromExtension('app', 'config', array( + 'charset' => 'UTF-8', + 'error_handler' => null, + 'csrf-protection' => array('enabled' => true, 'secret' => 'xxxxxxxxxx'), + 'router' => array('resource' => '%kernel.root_dir%/config/routing.php'), + // ... + )); + +In this case, the extension allows you to customize the ``charset``, ``error_handler``, +``csrf_protection``, ``router`` configuration and much more. Internally, +the ``FrameworkBundle`` uses the options specified here to define and configure +the services specific to it. The bundle takes care of creating all the necessary +``parameters`` and ``services`` for the service container, while still allowing +much of the configuration to be easily customized. As an added bonus, most +service container extensions are also smart enough to perform validation - +notifying you of missing options or options with the wrong data type. + +When installing or configuring a bundle, see the bundle's documentation for +how the services for the bundle should be installed and configured. The options +available for the core bundles can be found inside the :doc:`Reference Guide`. + +.. note:: + + Natively, the service container only recognizes the ``parameters``, + ``services``, ``imports`` and ``interfaces`` directives. Any other directives + are handled by a service container extension. + +.. index:: + single: Service Container; Referencing services + +Referencing (Injecting) Services +-------------------------------- + +So far, our original ``my_mailer`` service is simple: it takes just one argument +in its constructor, which is easily configurable. As you'll see, the real +power of the container is realized when you need to create a service that +depends on one or more other services in the container. + +Let's start with an example. Suppose we have a new service, ``NewsletterManager``, +that helps to manage the preparation and delivery of an email message to +a collection of addresses. Of course the ``my_mailer`` service is already +really good at delivering email messages, so we'll use it inside ``NewsletterManager`` +to handle the actual delivery of the messages. This pretend class might look +something like this:: + + namespace Sensio\HelloBundle\Newsletter; + use Sensio\HelloBundle\Mailer; + + class NewsletterManager + { + protected $mailer; + + public function __construct(Mailer $mailer) + { + $this->mailer = $mailer; + } + + // ... + } + +Without using the service container, we can create a new ``NewsletterManager`` +fairly easily from inside a controller:: + + public function sendNewsletterAction() + { + $mailer = $this->container->get('my_mailer'); + $newsletter = new Sensio\HelloBundle\Newsletter\NewsletterManager($mailer); + // ... + } + +This approach is fine, but what if we decide later that the ``NewsletterManager`` +class needs a second or third constructor argument? What if we decide to +refactor our code and rename the class? In both cases, you'd need to find every +place where the ``NewsletterManager`` is instantiated and modify it. Of course, +the service container gives us a much more appealing option: + +.. configuration-block:: + + .. code-block:: yaml + + # src/Sensio/HelloBundle/Resources/config/services.yml + parameters: + # ... + newsletter_manager.class: Sensio\HelloBundle\Newsletter\NewsletterManager + + services: + my_mailer: + # ... + newsletter_manager: + class: %newsletter_manager.class% + arguments: [@my_mailer] + + .. code-block:: xml + + + + + Sensio\HelloBundle\Newsletter\NewsletterManager + + + + + + + + + + + + .. code-block:: php + + // src/Sensio/HelloBundle/Resources/config/services.php + use Symfony\Component\DependencyInjection\Definition; + + // ... + $container->setParameter('newsletter_manager.class', 'Sensio\HelloBundle\Newsletter\NewsletterManager'); + + $container->setDefinition('my_mailer', ... ); + $container->setDefinition('newsletter_manager', new Definition( + '%newsletter_manager.class%', + array(new Reference('my_mailer')) + )); + +In YAML, the special ``@my_mailer`` syntax tells the container to look for +a service named ``my_mailer`` and to pass that object into the constructor +of ``NewsletterManager``. + +This is a very powerful tool that allows you to create independent service +classes with well-defined dependencies. In this example, the ``newsletter_manager`` +service needs the ``my_mailer`` service in order to function. When you define +this dependency in the service container, the container takes care of all +the work of instantiating the objects. + +Core Symfony and Third-Party Bundle Services +-------------------------------------------- + +Since Symfony2 and all third-party bundles configure and retrieve their services +via the container, you can easily access them or even use them in your own +services. For example, to handle the storage of information on a user's +session, Symfony2 provides a ``session`` service:: + + public function indexAction($bar) + { + $session = $this->container->get('session'); + $session->set('foo', $bar); + + // ... + } + +In Symfony2, you'll constantly use services provided by the Symfony core or +other third-party bundles to perform tasks such as rendering templates (``templating``), +sending emails (``mailer``), or accessing information on the request (``request``). + +We can take this a step further by using these services inside services that +you've created for your application. Let's modify the ``NewsletterManager`` +to use the real Symfony2 ``mailer`` service (instead of the pretend ``my_mailer``). +Let's also pass the templating engine service to the ``NewsletterManager`` +so that it can generate the email content via a template:: + + namespace Sensio\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; + } + + // ... + } + +Configuring the service container is easy: + +.. 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') + ) + )); + +The ``newsletter_manager`` service now has access to the core ``mailer`` +and ``templating`` services. This is a common way to create services specific +to your application that leverage the power of different services within +the framework. + +.. tip:: + + Be sure that ``swift_mailer.config`` entry appears in your application + configuration. As we mentioned in :ref:`service-container-extension-configuration`, + the ``swift_mailer.config`` invokes the service extension from the + ``SwiftmailerBundle``, which registers the ``mailer`` service. + +.. index:: + single: Service Container; Advanced configuration + +Advanced Container Configuration +-------------------------------- + +As we've seen, defining services inside the container is easy, generally +involving a ``service`` configuration key and a few parameters. However, +the container has several other tools available that help to *tag* services +for special functionality, create more complex services, and perform operations +after the container is built. + +Tags (``tags``) +~~~~~~~~~~~~~~~ + +In the same way that a blog post on the Web might be tagged with things such +as "Symfony" or "PHP", services configured in your container can also be +tagged. In the service container, a tag implies that the service is meant +to be used for a specific purpose. Take the following example: + +.. configuration-block:: + + .. code-block:: yaml + + services: + foo.twig.extension: + class: Sensio\HelloBundle\Extension\FooExtension + tags: + twig.extension: + name: twig.extension + + .. code-block:: xml + + + + + + .. code-block:: php + + $definition = new Definition('Sensio\HelloBundle\Extension\RadiusExtension'); + $definition->addTag('twig.extension'); + $container->setDefinition('foo.twig.extension', $definition); + +The ``twig.extension`` tag is a special tag that the ``TwigBundle`` uses +during configuration. By giving the service this ``twig.extension`` tag, +the bundle knows that the ``foo.twig.extension`` service should be registered +as a Twig extension with Twig. In other words, Twig finds all services tagged +with ``twig.extension`` and automatically registers them as extensions. + +Tags, then, are a way to tell Symfony2 or other third-party bundles that +your service should be registered or used in some special way by the bundle. + +The following is a list of the tags available with the core Symfony2 bundles. +Each of these has a different affect on your service and many tags require +additional arguments (beyond just the ``name`` attribute). + +* assetic.filter +* assetic.templating.php +* data_collector +* form.field_factory.guesser +* kernel.cache_warmer +* kernel.listener +* routing.loader +* security.listener.factory +* security.voter +* templating.helper +* twig.extension +* translation.loader +* validator.constraint_validator +* zend.logger.writer + +.. _`service-oriented architecture`: http://wikipedia.org/wiki/Service-oriented_architecture diff --git a/guides/testing/overview.rst b/guides/testing/overview.rst index 793072358f3..aa73244ad31 100644 --- a/guides/testing/overview.rst +++ b/guides/testing/overview.rst @@ -138,7 +138,7 @@ expression or a CSS selector, then use the Client to click on it:: Submitting a form is very similar; select a form button, optionally override some form values, and submit the corresponding form:: - $form = $crawler->selectButton('submit'); + $form = $crawler->selectButton('submit')->form(); // set some values $form['name'] = 'Lucas'; diff --git a/guides/tools/YAML.rst b/guides/tools/YAML.rst index 9d2fe62f22b..1a0504ebe40 100644 --- a/guides/tools/YAML.rst +++ b/guides/tools/YAML.rst @@ -241,7 +241,7 @@ A YAML file is rarely used to describe a simple scalar. Most of the time, it describes a collection. A collection can be a sequence or a mapping of elements. Both sequences and mappings are converted to PHP arrays. -Sequences use a dash followed by a space (``- ``): +Sequences use a dash followed by a space (``-`` ): .. code-block:: yaml @@ -253,7 +253,7 @@ The previous YAML file is equivalent to the following PHP code:: array('PHP', 'Perl', 'Python'); -Mappings use a colon followed by a space (``: ``) to mark each key/value pair: +Mappings use a colon followed by a space (``:`` ) to mark each key/value pair: .. code-block:: yaml diff --git a/index.rst b/index.rst index 2a5d6603587..634f9e6a8ee 100644 --- a/index.rst +++ b/index.rst @@ -28,6 +28,18 @@ Dive into Symfony2 with the topical guides: .. include:: guides/map.rst.inc +Reference Documents +------------------- + +Get answers quickly with reference documents: + +.. toctree:: + :hidden: + + reference/index + +.. include:: reference/map.rst.inc + Contributing ------------ diff --git a/quick_tour/ja/01-The-Big-Picture.markdown b/quick_tour/ja/01-The-Big-Picture.markdown new file mode 100644 index 00000000000..5896dc8e757 --- /dev/null +++ b/quick_tour/ja/01-The-Big-Picture.markdown @@ -0,0 +1,156 @@ +Symfony 2.0 のクイックツアー: 見取り図 +======================================== + +Symfony を試したいが10分ほどしか時間がない方へ。このチュートリアルの最初の部分はそんなあなたのために書かれました。シンプルな既成のプロジェクト構造を示すことで、Symfony でどのように速く始められるのかを説明します。 + +Web フレームワークを使ったことがあれば、Symfony 2.0 にしっくりくるでしょう。 + +ダウンロードとインストール +--------------------------- + +最初に、少なくとも PHP 5.3.0 がインストールされ Apache のような Web サーバーで動くように設定されているかチェックします。 + +用意はできましたか?Symfony をダウンロードして始めましょう。さらに速く始めるために、「Symfony サンドボックス」を使うことにします。これは Symfony のプロジェクトで、必須のすべてのライブラリとシンプルなコントローラがすでに収められており基本的なコンフィギュレーションの調整が行われています。サンドボックスがほかのインストール方法よりもすぐれている利点は Symfony ですぐに実験を始められることです。 + +[サンドボックス][1] をダウンロードし、Web 公開ディレクトリのルートで展開します。`sandbox/` ディレクトリが用意されています: + + www/ <- your web root directory + sandbox/ <- the unpacked archive + hello/ + cache/ + config/ + logs/ + src/ + Application/ + HelloBundle/ + Controller/ + Resources/ + vendor/ + symfony/ + web/ + +コンフィギュレーションをチェックする +--------------------------------------- + +もう少し先の作業で頭痛に悩まされないようにするために、次の URL をリクエストしてあなたのコンフィギュレーションで Symfony プロジェクトがスムーズに動くか確認します: + + http://localhost/sandbox/web/check.php + +スクリプトの出力を注意深く読み見つかる問題を修正します。 + +では、あなたの最初の「実際の」 Symfony の Web ページをリクエストします: + + http://localhost/sandbox/web/index_dev.php/ + +Symfony はあなたがこれまで頑張ったことをほめてくれます! + +最初のアプリケーション +------------------------ + +サンドボックスにはシンプルな実際の世界の「アプリケーション」が収納されており、これは Symfony をより詳しく学ぶためのものです。Symfony であいさつするために次の URL に移動します (Fabien をあなたのファーストネームに置き換える): + + http://localhost/sandbox/web/index_dev.php/hello/Fabien + +ここで何が起きているのでしょうか?URL を細かく調べましょう: + + * `index_dev.php`: これは「フロントコントローラ」です。これは hello アプリケーションへのユニークなエントリポイントですべてのユーザーリクエストに応答します; + + * `/hello/Fabien`: これはユーザーがアクセスしたいリソースへの「バーチャル」パスです。 + +あなたの開発者としての責務はユーザーリクエスト (`/hello/Fabien`) をそれに関連づけられているリソース (`Hello +Fabien!`) にマッピングするコードを書くことです。 + +### ルーティング + +しかし Symfony はどのようにしてリクエストをあなたのコードに転送するのでしょうか?単にルーティング設定ファイルを読み込むだけです: + + [yml] + # hello/config/routing.yml + homepage: + pattern: / + defaults: { _bundle: WebBundle, _controller: Default, _action: index } + + hello: + resource: HelloBundle/Resources/config/routing.yml + +[YAML](http://www.yaml.org/) で書かれているファイル, コンフィギュレーションの設定項目の記述をとても簡単にするフォーマットです。Symfony のすべての設定ファイルはcan be written in XML、YAML、もしくはプレーンな PHP コードで書かれています。このチュートリアルでは簡潔性と初心者が読みやすいように YAML フォーマットを使います。もちろん、「企業アプリケーションを開発している方々」はどこでも XML を使ってきました。 + +ルーティング設定ファイルの最初の3行はユーザーが 「`/`」 リソースをリクエストするときに呼び出すコードを定義します。 より興味深いのは最後の行で、次のような内容を読み込む別のルーティング設定ファイルをインポートしています: + + [yml] + # src/Application/HelloBundle/Resources/config/routing.yml + hello: + pattern: /hello/:name + defaults: { _bundle: HelloBundle, _controller: Hello, _action: index } + +さあ始めましょう!ご覧のとおり、「`/hello/:name`」リソースパターン (`:name` のようにコロンで始まる文字列はプレースホルダー) はコントローラにマッピングされ、`_bundle`、`_controller`、 `_action` 変数によって参照されます。 + +### コントローラ + +コントローラはリソースの表現 (ほとんどの場合 HTML リソース) を返す責務を担います。これは PHP クラスとして定義されます: + + [php] + # src/Application/HelloBundle/Controller/HelloController.php + namespace Application\HelloBundle\Controller; + + use Symfony\Framework\WebBundle\Controller; + + class HelloController extends Controller + { + public function indexAction($name) + { + return $this->render('HelloBundle:Hello:index', array('name' => $name)); + } + } + +コードはとても直感的ですがこのコードを一行ごとに説明しましょう: + + * `namespace Application\HelloBundle\Controller;`: Symfony は PHP 5.3 の新しい機能を利用するので、すべてのコントローラには適切な名前空間がつけられています (名前空間は `_bundle` のルーティング値: `HelloBundle` を含む)。 + + * `class HelloController extends Controller`: コントローラの名前は `_controller` ルーティング値 (`Hello`) と `Controller` を連結したものです。これは便利なショートカットを提供する組み込みの `Controller` クラスを継承します (このチュートリアルの後のほうで見ます)。 + + * `public function indexAction($name)`: それぞれのコントローラは複数のアクションで構成されます。コンフィギュレーションごとに、hello ページは `index` アクション (`_action` ルーティング値) によって処理されます。このメソッドは引数としてリソースのプレースホルダの値 (このケースでは `$name`) を受け取ります。 + + * `return $this->render('HelloBundle:Hello:index', array('name' => $name));`: + `render()` メソッドはテンプレートをロードし2番目の引数として渡される変数 (`HelloBundle:Hello:index`) でテンプレートをレンダリングします。 + +しかしバンドルとは何でしょうか?プロジェクトで書くすべてコードはバンドルに編成されます。Symfony の言い回しでは、バンドルはファイルの構造化された集まり (PHP +ファイル、スタイルシート、JavaScripts、画像、・・・) で単独の機能 +(ブログ、フォーラム、・・・) を実装し、ほかの開発者と簡単に共有できます。私たちの例では、`HelloBundle` だけが用意されています。 + +### テンプレート + +ですので、コントローラは `HelloBundle:Hello:index` テンプレートをレンダリングします。しかしテンプレートの名前のなかになるものは何でしょうか?`HelloBundle` はバンドルの名前で、`Hello` はコントローラで、`index` はテンプレートの名前です。テンプレート自身は HTML とシンプルな PHP の式で構成されます: + + [php] + # src/Application/HelloBundle/Resources/views/Hello/index.php + extend('HelloBundle::layout') ?> + + Hello ! + +おめでとうございます!最初の Symfony のコードピースを見ました。これはそれほどむずかしくなかったでしょう?Symfony は Web サイトをよりよく速く実装できるようにしてくれます。 + +環境 +---- + +これで Symfony がどのように動くのか理解が進みました。このチュートリアルを読み進めましょう; Symfony +と PHP ロゴがある小さなバーに気づくでしょう。これは「Web デバッグツールバー」と呼ばれ開発者の最良の友です。もちろん、アプリケーションを運用サーバーにデプロイするときにはこのようなツールは表示してはなりません。これが `web/` ディレクトリのなかに運用環境に合わせて最適化された別のフロントコントローラ (`index.php`) が見つかる理由です: + + http://localhost/sandbox/web/index.php/hello/Fabien + +`mod_rewrite` をインストールした場合、URL の `index.php` の部分を省略することができます: + + http://localhost/sandbox/web/hello/Fabien + +いい忘れていましたが、運用環境では、インストールするアプリケーションを安全にして URL の見た目をよくするために Web ルートディレクトリに `web/` ディレクトリを指定すべきです: + + http://localhost/hello/Fabien + +運用環境のアプリケーションをできるかぎり速く動かすために、Symfony は `hello/cache/` ディレクトリのもとでキャッシュを維持しますので、キャッシュ済みファイルを手動で削除する必要があります。これがプロジェクトで作業するとき、開発環境のフロントコントローラ (`index_dev.php`) をつねに使うべき理由です。 + +考察 +----- + +10分がすぎました。これで、独自のシンプルなルート、コントローラ、とテンプレートを作ることができるようになります。練習として、Hello アプリケーションよりもっと便利なものを作ってみてください!Symfony を詳しく学びたいのであれば、このチュートリアルのすぐ次のチュートリアルを読めば、テンプレートシステムについて詳しく学ぶことができます。 + +[1]: http://symfony-reloaded.org/code#sandbox diff --git a/quick_tour/the_big_picture.rst b/quick_tour/the_big_picture.rst index f1c0d685869..d800c95af6f 100644 --- a/quick_tour/the_big_picture.rst +++ b/quick_tour/the_big_picture.rst @@ -51,14 +51,15 @@ should now have a ``sandbox/`` directory:: Checking the Configuration -------------------------- -To avoid some headaches further down the line, check that your configuration -can run a Symfony2 project smoothly by requesting the following URL: +Symfony2 comes with a visual server configuration tester to help avoid some +headaches that come from web server or php misconfiguration. Please use +the following url to see the diagnostics for your server: http://localhost/sandbox/web/check.php -Read the script output carefully and fix any problem that it finds. +Read the script output carefully and correct any oustanding issues. -Now, request your first "real" Symfony2 webpage: +Now you can request your first "real" Symfony2 webpage: http://localhost/sandbox/web/app_dev.php/ @@ -67,9 +68,9 @@ Symfony2 should congratulate you for your hard work so far! Creating your first Application ------------------------------- -The sandbox comes with a simple Hello World ":term:`application`" and that's -the application we will use to learn more about Symfony2. Go to the following -URL to be greeted by Symfony2 (replace Fabien with your first name): +The sandbox comes with a simple Hello World ":term:`application`" we will use +to learn more about Symfony2. Go to the following URL to be greeted by Symfony2 +(replace Fabien with your first name): http://localhost/sandbox/web/app_dev.php/hello/Fabien @@ -80,7 +81,7 @@ What's going on here? Let's dissect the URL: * ``app_dev.php``: This is a "front controller". It is the unique entry point of the application and it responds to all user requests; -* ``/hello/Fabien``: This is the "virtual" path to the resource the user wants +* ``/hello/Fabien``: This is the virtual path to the resource the user wants to access. Your responsibility as a developer is to write the code that maps the user @@ -93,19 +94,14 @@ Fabien!``). Configuration ~~~~~~~~~~~~~ -But how does Symfony2 route the request to your code? Simply by reading some -configuration file. - -All Symfony2 configuration files can be written in either PHP, XML, or `YAML`_ -(YAML is a simple format that makes the description of configuration settings -straightforward). +Symfony2 configuration files can be written in PHP, XML or `YAML`_. The +different types are compatible and may be used interchangeably within an +application. .. tip:: The sandbox defaults to YAML, but you can easily switch to XML or PHP by - editing the ``app/AppKernel.php`` file. You can switch now by looking at - the bottom of this file for instructions (the tutorials show the - configuration for all supported formats). + editing the ``app/AppKernel.php`` file. .. index:: single: Routing @@ -114,7 +110,8 @@ straightforward). Routing ~~~~~~~ -So, Symfony2 routes the request by reading the routing configuration file: +Symfony2 routes the request to your code by using a configuration file. Here +are a few examples of the routing configuration file for our application: .. configuration-block:: @@ -158,9 +155,14 @@ So, Symfony2 routes the request by reading the routing configuration file: return $collection; -The first few lines of the routing configuration file define the code called -when the user requests the "``/``" resource. More interesting is the last -part, which imports another routing configuration file that reads as follows: +The first few lines of the routing configuration file define the code that is +executed when the user requests the "``/``" resource. + +.. tip:: + + If you're comfortable with routing, have a look at the last directive of + the configuration file. Symfony2 can include routing information from + other bundles. .. configuration-block:: @@ -198,7 +200,7 @@ part, which imports another routing configuration file that reads as follows: return $collection; -Here we go! As you can see, the "``/hello/{name}``" resource pattern (a string +As you can see, the "``/hello/{name}``" resource pattern (a string enclosed in curly brackets like ``{name}`` is a placeholder) is mapped to a controller, referenced by the ``_controller`` value. @@ -209,8 +211,8 @@ controller, referenced by the ``_controller`` value. Controllers ~~~~~~~~~~~ -The controller is responsible for returning a representation of the resource -(most of the time an HTML one) and it is defined as a PHP class: +The controller defines actions to handle users requests and prepares responses +(often in HTML). .. code-block:: php :linenos: @@ -234,21 +236,22 @@ The controller is responsible for returning a representation of the resource The code is pretty straightforward but let's explain it line by line: -* *line 3*: Symfony2 takes advantage of new PHP 5.3 features and as such, all - controllers are properly namespaced (the namespace is the first part of the - ``_controller`` routing value: ``HelloBundle``). +* *line 3*: Symfony2 takes advantage of new PHP 5.3 namespacing features, + and all controllers should be properly namespaced (though this is not + required). In this example, the controller lives in the bundle named ``HelloBundle``, + which forms the first part of the ``_controller`` routing value. -* *line 7*: The controller name is the concatenation of the second part of the - ``_controller`` routing value (``Hello``) and ``Controller``. It extends the - built-in ``Controller`` class, which provides useful shortcuts (as we will - see later in this tutorial). +* *line 7*: The controller name is the combination of the second part of the + ``_controller`` routing value (``Hello``) and the word ``Controller``. It + extends the built-in ``Controller`` class, which provides useful shortcuts + (as we will see later in this tutorial). -* *line 9*: Each controller is made of several actions. As per the +* *line 9*: Each controller is made of several actions. As per the routing configuration, the hello page is handled by the ``index`` action (the third part of the ``_controller`` routing value). This method receives the - resource placeholder values as arguments (``$name`` in our case). + placeholder values as arguments (``$name`` in our case). -* *line 11*: The ``render()`` method loads and renders a template +* *line 11*: The ``render()`` method loads and renders a template file (``HelloBundle:Hello:index.html.twig``) with the variables passed as a second argument. @@ -258,13 +261,18 @@ organized in bundles. In Symfony2 speak, a bundle is a structured set of files feature (a blog, a forum, ...) and which can be easily shared with other developers. In our example, we only have one bundle, ``HelloBundle``. +.. tip:: + + In general, controller actions should be as short as possible. If one is + getting too long, consider refactoring some of the more complicated code to + the service layer (which will be discussed later). + Templates ~~~~~~~~~ -So, the controller renders the ``HelloBundle:Hello:index.html.twig`` template. -But what's in a template name? ``HelloBundle`` is the bundle name, ``Hello`` -is the controller, and ``index.html.twig`` the template name. By default, the -sandbox uses Twig as its template engine: +The controller renders the ``HelloBundle:Hello:index.html.twig`` template. By +default, the sandbox uses Twig as its template engine but you can also use +traditional PHP templates if you choose. .. code-block:: jinja @@ -275,10 +283,6 @@ sandbox uses Twig as its template engine: Hello {{ name }}! {% endblock %} -Congratulations! You have looked at your first Symfony2 piece of code. That was -not so hard, was it? Symfony2 makes it really easy to implement web sites -better and faster. - .. index:: single: Environment single: Configuration; Environment @@ -286,13 +290,12 @@ better and faster. Working with Environments ------------------------- -Now that you have a better understanding on how Symfony2 works, have a closer +Now that you have a better understanding of how Symfony2 works, have a closer look at the bottom of the page; you will notice a small bar with the Symfony2 -and PHP logos. It is called the "Web Debug Toolbar" and it is the developer's +and PHP logos. This is called the "Web Debug Toolbar" and it is the developer's best friend. Of course, such a tool must not be displayed when you deploy your -application to your production servers. That's why you will find another front -controller in the ``web/`` directory (``app.php``), optimized for the -production environment: +application to production. That's why you will find another front controller in +the ``web/`` directory (``app.php``), optimized for the production environment: http://localhost/sandbox/web/app.php/hello/Fabien @@ -309,18 +312,17 @@ better looking URL: To make the production environment as fast as possible, Symfony2 maintains a cache under the ``app/cache/`` directory. When you make changes to the code or -configuration, you need to manually remove the cached files. That's why you -should always use the development front controller (``app_dev.php``) when -working on a project. +configuration, you need to manually remove the cached files. The development +front controller (``app_dev.php``) does not use this cache and your changes +appear immediately, thus is recommended when working on a project. Final Thoughts -------------- -The 10 minutes are over. By now, you should be able to create your own simple -routes, controllers, and templates. As an exercise, try to build something -more useful than the Hello application! But if you are eager to learn more -about Symfony2, you can read the next part of this tutorial right away, where -we dive more into the templating system. +Thanks for trying out Symfony2! By now, you should be able to create your own +simple routes, controllers and templates. As an exercise, try to build +something more useful than the Hello application! If you are eager to +learn more about Symfony2, dive into the next section: the view system. .. _sandbox: http://symfony-reloaded.org/code#sandbox .. _YAML: http://www.yaml.org/ diff --git a/reference/index.rst b/reference/index.rst new file mode 100644 index 00000000000..63f285a9739 --- /dev/null +++ b/reference/index.rst @@ -0,0 +1,6 @@ +Reference Documents +=================== + +Get answers quickly with reference documents: + +.. include:: map.rst.inc diff --git a/reference/map.rst.inc b/reference/map.rst.inc new file mode 100644 index 00000000000..2c18c4d22bc --- /dev/null +++ b/reference/map.rst.inc @@ -0,0 +1,13 @@ +* **Bundle Configuration**: + + * :doc:`FrameworkBundle (app.config) ` | + * :doc:`DoctrineBundle (doctrine.*) ` | + * :doc:`DoctrineMongoDBBundle (doctrine_odm.mongodb) ` | + * :doc:`SecurityBundle (security.*) ` | + * :doc:`FrameworkBundle (swiftmailer.config) ` | + * :doc:`TwigBundle (twig.config) ` | + * :doc:`WebProfilerBundle (webprofiler.config) ` | + * :doc:`ZendBundle (zend.config) ` + +* :doc:`/guides/forms/fields/index` +* :doc:`/guides/validator/constraints/index` diff --git a/src/vendor/doctrine-mongodb/LICENSE b/src/vendor/doctrine-mongodb/LICENSE new file mode 100644 index 00000000000..feb7925e6f5 --- /dev/null +++ b/src/vendor/doctrine-mongodb/LICENSE @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/README.markdown b/src/vendor/doctrine-mongodb/README.markdown new file mode 100644 index 00000000000..34fae0462b1 --- /dev/null +++ b/src/vendor/doctrine-mongodb/README.markdown @@ -0,0 +1,3 @@ +# Doctrine MongoDB Object Document Mapper + +All available documentation can be found [here](http://www.doctrine-project.org/projects/mongodb_odm/1.0/docs/en). \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/TODO b/src/vendor/doctrine-mongodb/TODO new file mode 100644 index 00000000000..e476fff5b79 --- /dev/null +++ b/src/vendor/doctrine-mongodb/TODO @@ -0,0 +1,64 @@ +# TODO List + +List of features to implement. + +## Indexing Documents + +* Putting @Indexed or @Unindexed on a class (document or embedded) will make all of its fields default to indexed or unindexed, respectively. +* Putting @Indexed or @Unindexed on a field will make it indexed or unindexed, respectively. +* @Indexed or @Unindexed status for nested classes and fields are generally inherited from containing fields and classes, except that: + * @Indexed or @Unindexed on a field overrides the default of the class containing the field. + * @Indexed or @Unindexed on a field of type @Embedded will override the default on the class inside the field (be it a single class or a collection). + +- + + [php] + /** @Document */ + class LevelTwo + { + /** + * @Field + * @Indexed + */ + private $gamma; + + /** @Field */ + private $delta; + + /** + * @EmbedOne(targetDocument="Profile") + */ + private $profile; + } + + /** + * @EmbeddedDocument + * @Indexed + */ + class LevelOne + { + /** @Field */ + private $beta; + + /** + * @Field + * @Unindexed + * @EmbedOne(targetDocument="LevelTwo") + */ + private $two; + } + + /** + * @Document + */ + class DocumentWithComplicatedIndexing + { + /** @Id */ + private $id; + + /** @EmbedOne(targetDocument="LevelOne") */ + private $one; + + /** @Field */ + private $alpha; + } \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/doctrine-mongo-mapping.xsd b/src/vendor/doctrine-mongodb/doctrine-mongo-mapping.xsd new file mode 100755 index 00000000000..411a495bfb9 --- /dev/null +++ b/src/vendor/doctrine-mongodb/doctrine-mongo-mapping.xsd @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/CollectionEvents.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/CollectionEvents.php new file mode 100644 index 00000000000..372086adffd --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/CollectionEvents.php @@ -0,0 +1,56 @@ +. + */ + +namespace Doctrine\ODM\MongoDB; + +/** + * Container for all MongoCollection events. + * + * This class cannot be instantiated. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 1.0 + * @author Jonathan H. Wage + */ +final class CollectionEvents +{ + const preBatchInsert = 'collectionPreBatchInsert'; + const postBatchInsert = 'collectionPostBatchInsert'; + + const preUpdate = 'collectionPreUpdate'; + const postUpdate = 'collectionPostUpdate'; + + const preSaveFile = 'collectionPreSaveFile'; + const postSaveFile = 'collectionPostSaveFile'; + + const preGetDBRef = 'collectionPreGetDBRef'; + const postGetDBRef = 'collectionPostGetDBRef'; + + const preSave = 'collectionPreSave'; + const postSave = 'collectionPostSave'; + + const preFind = 'collectionPreFind'; + const postFind = 'collectionPostFind'; + + const preFindOne = 'collectionPreFindOne'; + const postFindOne = 'collectionPostFindOne'; +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Configuration.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Configuration.php new file mode 100644 index 00000000000..6a2d58f9b5d --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Configuration.php @@ -0,0 +1,340 @@ +. + */ + +namespace Doctrine\ODM\MongoDB; + +use Doctrine\ODM\MongoDB\Mapping\Driver\Driver, + Doctrine\ODM\MongoDB\Mapping\Driver\PHPDriver, + Doctrine\Common\Cache\Cache; + +/** + * Configuration class for the DocumentManager. When setting up your DocumentManager + * you can optionally specify an instance of this class as the second argument. + * If you do not pass a configuration object, a blank one will be created for you. + * + * + * @author Roman Borschel + */ +class Configuration +{ + /** + * Array of attributes for this configuration instance. + * + * @var array $_attributes + */ + private $_attributes = array('mongoCmd' => '$'); + + /** + * Create a new Configuration instance. + */ + public function __construct() + { + $this->_attributes['metadataDriverImpl'] = new PHPDriver(); + } + + /** + * Adds a namespace under a certain alias. + * + * @param string $alias + * @param string $namespace + */ + public function addDocumentNamespace($alias, $namespace) + { + $this->_attributes['documentNamespaces'][$alias] = $namespace; + } + + /** + * Resolves a registered namespace alias to the full namespace. + * + * @param string $documentNamespaceAlias + * @return string + * @throws MappingException + */ + public function getDocumentNamespace($documentNamespaceAlias) + { + if ( ! isset($this->_attributes['documentNamespaces'][$documentNamespaceAlias])) { + throw MongoDBException::unknownDocumentNamespace($documentNamespaceAlias); + } + + return trim($this->_attributes['documentNamespaces'][$documentNamespaceAlias], '\\'); + } + + /** + * Set the document alias map + * + * @param array $documentAliasMap + * @return void + */ + public function setDocumentNamespaces(array $documentNamespaces) + { + $this->_attributes['documentNamespaces'] = $documentNamespaces; + } + + /** + * Sets the cache driver implementation that is used for metadata caching. + * + * @param Driver $driverImpl + * @todo Force parameter to be a Closure to ensure lazy evaluation + * (as soon as a metadata cache is in effect, the driver never needs to initialize). + */ + public function setMetadataDriverImpl(Driver $driverImpl) + { + $this->_attributes['metadataDriverImpl'] = $driverImpl; + } + + /** + * Gets the cache driver implementation that is used for the mapping metadata. + * + * @return Mapping\Driver\Driver + */ + public function getMetadataDriverImpl() + { + return isset($this->_attributes['metadataDriverImpl']) ? + $this->_attributes['metadataDriverImpl'] : null; + } + + /** + * Gets the cache driver implementation that is used for metadata caching. + * + * @return \Doctrine\Common\Cache\Cache + */ + public function getMetadataCacheImpl() + { + return isset($this->_attributes['metadataCacheImpl']) ? + $this->_attributes['metadataCacheImpl'] : null; + } + + /** + * Sets the cache driver implementation that is used for metadata caching. + * + * @param \Doctrine\Common\Cache\Cache $cacheImpl + */ + public function setMetadataCacheImpl(Cache $cacheImpl) + { + $this->_attributes['metadataCacheImpl'] = $cacheImpl; + } + + /** + * Sets the directory where Doctrine generates any necessary proxy class files. + * + * @param string $dir + */ + public function setProxyDir($dir) + { + $this->_attributes['proxyDir'] = $dir; + } + + /** + * Gets the directory where Doctrine generates any necessary proxy class files. + * + * @return string + */ + public function getProxyDir() + { + return isset($this->_attributes['proxyDir']) ? + $this->_attributes['proxyDir'] : null; + } + + /** + * Gets a boolean flag that indicates whether proxy classes should always be regenerated + * during each script execution. + * + * @return boolean + */ + public function getAutoGenerateProxyClasses() + { + return isset($this->_attributes['autoGenerateProxyClasses']) ? + $this->_attributes['autoGenerateProxyClasses'] : true; + } + + /** + * Sets a boolean flag that indicates whether proxy classes should always be regenerated + * during each script execution. + * + * @param boolean $bool + */ + public function setAutoGenerateProxyClasses($bool) + { + $this->_attributes['autoGenerateProxyClasses'] = $bool; + } + + /** + * Gets the namespace where proxy classes reside. + * + * @return string + */ + public function getProxyNamespace() + { + return isset($this->_attributes['proxyNamespace']) ? + $this->_attributes['proxyNamespace'] : null; + } + + /** + * Sets the namespace where proxy classes reside. + * + * @param string $ns + */ + public function setProxyNamespace($ns) + { + $this->_attributes['proxyNamespace'] = $ns; + } + + /** + * Sets the default DB to use for all Documents that do not specify + * a database. + * + * @param string $defaultDB + */ + public function setDefaultDB($defaultDB) + { + $this->_attributes['defaultDB'] = $defaultDB; + } + + /** + * Gets the default DB to use for all Documents that do not specify a database. + * + * @return string $defaultDB + */ + public function getDefaultDB() + { + return isset($this->_attributes['defaultDB']) ? + $this->_attributes['defaultDB'] : null; + } + + /** + * Sets the environment + * + * @param string $environment + */ + public function setEnvironment($environment) + { + $this->_attributes['environment'] = $environment; + } + + /** + * Gets the environment + * + * @return string $environment + */ + public function getEnvironment() + { + return isset($this->_attributes['environment']) ? + $this->_attributes['environment'] : null; + } + + /** + * Gets prefix for environment + * + * @return string $envPrefix + */ + public function getEnvironmentPrefix() + { + return isset($this->_attributes['environment']) ? + sprintf('%s_', $this->_attributes['environment']) : null; + } + + /** + * Set the logger callable. + * + * @param mixed $loggerCallable The logger callable. + */ + public function setLoggerCallable($loggerCallable) + { + $this->_attributes['loggerCallable'] = $loggerCallable; + } + + /** + * Gets the logger callable. + * + * @return mixed $loggerCallable The logger callable. + */ + public function getLoggerCallable() + { + return isset($this->_attributes['loggerCallable']) ? + $this->_attributes['loggerCallable'] : null; + } + + /** + * Set prefix for db name + * + * @param string $prefix The prefix for names of databases + */ + public function setDBPrefix($prefix = null) + { + $this->_attributes['dbPrefix'] = $prefix; + } + + /** + * Get prefix for db name + * + * @return string + */ + public function getDBPrefix() + { + return isset($this->_attributes['dbPrefix']) ? + $this->_attributes['dbPrefix'] : null; + } + + /** + * Set suffix for db name + * + * @param string $suffix The suffix for names of tables + */ + public function setDBSuffix($suffix = null) + { + $this->_attributes['dbSuffix'] = $suffix; + } + + /** + * Get suffix for db name + * + * @return string + */ + public function getDBSuffix() + { + return isset($this->_attributes['dbSuffix']) ? + $this->_attributes['dbSuffix'] : null; + } + + /** + * Get mongodb command prefix - '$' by default + * @return string + */ + public function getMongoCmd() + { + return $this->_attributes['mongoCmd']; + } + + /** + * Set mongodb command prefix + * @param string $cmd + */ + public function setMongoCmd($cmd) + { + $this->_attributes['mongoCmd'] = $cmd; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/DocumentManager.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/DocumentManager.php new file mode 100644 index 00000000000..944d6e73f6d --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/DocumentManager.php @@ -0,0 +1,613 @@ +. + */ + +namespace Doctrine\ODM\MongoDB; + +use Doctrine\ODM\MongoDB\Mapping\ClassMetadata, + Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactory, + Doctrine\ODM\MongoDB\Mapping\Driver\PHPDriver, + Doctrine\ODM\MongoDB\Query, + Doctrine\ODM\MongoDB\Mongo, + Doctrine\ODM\MongoDB\PersistentCollection, + Doctrine\ODM\MongoDB\Proxy\ProxyFactory, + Doctrine\ODM\MongoDB\Query\Parser, + Doctrine\Common\Collections\ArrayCollection, + Doctrine\Common\EventManager; + +/** + * The DocumentManager class is the central access point for managing the + * persistence of documents. + * + * + * @author Roman Borschel + */ +class DocumentManager +{ + /** + * The Doctrine Mongo wrapper instance + * + * @var Doctrine\ODM\MongoDB\Mongo + */ + private $_mongo; + + /** + * The used Configuration. + * + * @var Doctrine\ODM\MongoDB\Configuration + */ + private $_config; + + /** + * The metadata factory, used to retrieve the ORM metadata of document classes. + * + * @var Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactory + */ + private $_metadataFactory; + + /** + * The DocumentRepository instances. + * + * @var array + */ + private $_repositories = array(); + + /** + * The UnitOfWork used to coordinate object-level transactions. + * + * @var Doctrine\ODM\MongoDB\UnitOfWork + */ + private $_unitOfWork; + + /** + * The event manager that is the central point of the event system. + * + * @var Doctrine\Common\EventManager + */ + private $_eventManager; + + /** + * The Document hydrator instance. + * + * @var Doctrine\ODM\MongoDB\Hydrator + */ + private $_hydrator; + + /** + * Array of cached MongoDB instances that are lazily loaded. + * + * @var array + */ + private $_documentDBs = array(); + + /** + * Array of cached MongoCollection instances that are lazily loaded. + * + * @var array + */ + private $_documentCollections = array(); + + /** + * The Query\Parser instance for parsing string based queries. + * + * @var Query\Parser $parser + */ + private $_queryParser; + + /** + * Whether the DocumentManager is closed or not. + */ + private $_closed = false; + + /** + * Creates a new Document that operates on the given Mongo connection + * and uses the given Configuration. + * + * @param Doctrine\ODM\MongoDB\Mongo $mongo + * @param Doctrine\ODM\MongoDB\Configuration $config + * @param Doctrine\Common\EventManager $eventManager + */ + protected function __construct(Mongo $mongo = null, Configuration $config = null, EventManager $eventManager = null) + { + if (is_string($mongo) || $mongo instanceof \Mongo) { + $mongo = new Mongo($mongo); + } + $this->_mongo = $mongo ? $mongo : new Mongo(); + $this->_config = $config ? $config : new Configuration(); + $this->_eventManager = $eventManager ? $eventManager : new EventManager(); + $this->_hydrator = new Hydrator($this); + $this->_metadataFactory = new ClassMetadataFactory($this); + if ($cacheDriver = $this->_config->getMetadataCacheImpl()) { + $this->_metadataFactory->setCacheDriver($cacheDriver); + } + $this->_queryParser = new Parser($this); + $this->_unitOfWork = new UnitOfWork($this); + $this->_proxyFactory = new ProxyFactory($this, + $config->getProxyDir(), + $config->getProxyNamespace(), + $config->getAutoGenerateProxyClasses()); + } + + /** + * Creates a new Document that operates on the given Mongo connection + * and uses the given Configuration. + * + * @param Doctrine\ODM\MongoDB\Mongo $mongo + * @param Doctrine\ODM\MongoDB\Configuration $config + * @param Doctrine\Common\EventManager $eventManager + */ + public static function create(Mongo $mongo, Configuration $config = null, EventManager $eventManager = null) + { + return new self($mongo, $config, $eventManager); + } + + /** + * Determines whether an document instance is managed in this DocumentManager. + * + * @param object $document + * @return boolean TRUE if this DocumentManager currently manages the given document, FALSE otherwise. + */ + public function contains($document) + { + return $this->_unitOfWork->isScheduledForInsert($document) || + $this->_unitOfWork->isInIdentityMap($document) && + ! $this->_unitOfWork->isScheduledForDelete($document); + } + + /** + * Gets the EventManager used by the DocumentManager. + * + * @return Doctrine\Common\EventManager + */ + public function getEventManager() + { + return $this->_eventManager; + } + + public function getConfiguration() + { + return $this->_config; + } + + public function getMongo() + { + return $this->_mongo; + } + + /** + * Gets the metadata factory used to gather the metadata of classes. + * + * @return Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactory + */ + public function getMetadataFactory() + { + return $this->_metadataFactory; + } + + /** + * Gets the UnitOfWork used by the DocumentManager to coordinate operations. + * + * @return Doctrine\ODM\MongoDB\UnitOfWork + */ + public function getUnitOfWork() + { + return $this->_unitOfWork; + } + + /** + * Gets the Hydrator used by the DocumentManager to hydrate document arrays + * to document objects. + * + * @return Doctrine\ODM\MongoDB\Hydrator + */ + public function getHydrator() + { + return $this->_hydrator; + } + + /** + * Returns the metadata for a class. + * + * @param string $className The class name. + * @return Doctrine\ODM\MongoDB\Mapping\ClassMetadata + * @internal Performance-sensitive method. + */ + public function getClassMetadata($className) + { + return $this->_metadataFactory->getMetadataFor($className); + } + + /** + * Returns the MongoDB instance for a class. + * + * @param string $className The class name. + * @return Doctrine\ODM\MongoDB\MongoDB + */ + public function getDocumentDB($className) + { + $db = $this->_metadataFactory->getMetadataFor($className)->getDB(); + $db = $db ? $db : $this->_config->getDefaultDB(); + $db = $db ? $db : 'doctrine'; + $db = sprintf('%s%s', $this->_config->getEnvironmentPrefix(), $db); + if ($db && ! isset($this->_documentDBs[$db])) { + $database = $this->_mongo->selectDB($db); + $this->_documentDBs[$db] = new MongoDB($database); + } + if ( ! isset($this->_documentDBs[$db])) { + throw MongoDBException::documentNotMappedToDB($className); + } + return $this->_documentDBs[$db]; + } + + /** + * Returns the MongoCollection instance for a class. + * + * @param string $className The class name. + * @return Doctrine\ODM\MongoDB\MongoCollection + */ + public function getDocumentCollection($className) + { + $metadata = $this->_metadataFactory->getMetadataFor($className); + $collection = $metadata->getCollection(); + $db = $metadata->getDB(); + $key = $db . '.' . $collection; + if ($collection && ! isset($this->_documentCollections[$key])) { + if ($metadata->isFile()) { + $collection = $this->getDocumentDB($className)->getGridFS($collection); + } else { + $collection = $this->getDocumentDB($className)->selectCollection($collection); + } + $mongoCollection = new MongoCollection($collection, $metadata, $this); + $this->_documentCollections[$key] = $mongoCollection; + } + if ( ! isset($this->_documentCollections[$key])) { + throw MongoDBException::documentNotMappedToCollection($className); + } + return $this->_documentCollections[$key]; + } + + public function query($queryString, $parameters = array()) + { + if ( ! is_array($parameters)) { + $parameters = array($parameters); + } + return $this->_queryParser->parse($queryString, $parameters); + } + + /** + * Create a new Query instance for a class. + * + * @param string $className The class name. + * @return Document\ODM\MongoDB\Query + */ + public function createQuery($className = null) + { + return new Query($this, $className); + } + + /** + * Tells the DocumentManager to make an instance managed and persistent. + * + * The document will be entered into the database at or before transaction + * commit or as a result of the flush operation. + * + * NOTE: The persist operation always considers documents that are not yet known to + * this DocumentManager as NEW. Do not pass detached documents to the persist operation. + * + * @param object $document The instance to make managed and persistent. + */ + public function persist($document) + { + if ( ! is_object($document)) { + throw new \InvalidArgumentException(gettype($document)); + } + $this->_errorIfClosed(); + $this->_unitOfWork->persist($document); + } + + /** + * Removes a document instance. + * + * A removed document will be removed from the database at or before transaction commit + * or as a result of the flush operation. + * + * @param object $document The document instance to remove. + */ + public function remove($document) + { + if ( ! is_object($document)) { + throw new \InvalidArgumentException(gettype($document)); + } + $this->_errorIfClosed(); + $this->_unitOfWork->remove($document); + } + + /** + * Refreshes the persistent state of a document from the database, + * overriding any local changes that have not yet been persisted. + * + * @param object $document The document to refresh. + */ + public function refresh($document) + { + if ( ! is_object($document)) { + throw new \InvalidArgumentException(gettype($document)); + } + $this->_errorIfClosed(); + $this->_unitOfWork->refresh($document); + } + + /** + * Detaches a document from the DocumentManager, causing a managed document to + * become detached. Unflushed changes made to the document if any + * (including removal of the document), will not be synchronized to the database. + * Documents which previously referenced the detached document will continue to + * reference it. + * + * @param object $document The document to detach. + */ + public function detach($document) + { + if ( ! is_object($document)) { + throw new \InvalidArgumentException(gettype($document)); + } + $this->_unitOfWork->detach($document); + } + + /** + * Merges the state of a detached document into the persistence context + * of this DocumentManager and returns the managed copy of the document. + * The document passed to merge will not become associated/managed with this DocumentManager. + * + * @param object $document The detached document to merge into the persistence context. + * @return object The managed copy of the document. + */ + public function merge($document) + { + if ( ! is_object($document)) { + throw new \InvalidArgumentException(gettype($document)); + } + $this->_errorIfClosed(); + return $this->_unitOfWork->merge($document); + } + + /** + * Gets the repository for a document class. + * + * @param string $documentName The name of the Document. + * @return DocumentRepository The repository. + */ + public function getRepository($documentName) + { + if (isset($this->_repositories[$documentName])) { + return $this->_repositories[$documentName]; + } + + $metadata = $this->getClassMetadata($documentName); + $customRepositoryClassName = $metadata->customRepositoryClassName; + + if ($customRepositoryClassName !== null) { + $repository = new $customRepositoryClassName($this, $metadata); + } else { + $repository = new DocumentRepository($this, $metadata); + } + + $this->_repositories[$documentName] = $repository; + + return $repository; + } + + /** + * Loads a given document by its ID refreshing the values with the data from + * the database if the document already exists in the identity map. + * + * @param string $documentName The document name to load. + * @param string $id The id the document to load. + * @return object $document The loaded document. + * @todo this function seems to be doing to much, should we move parts of it + * to BasicDocumentPersister maybe? + */ + public function loadByID($documentName, $id) + { + $class = $this->getClassMetadata($documentName); + $collection = $this->getDocumentCollection($documentName); + + $result = $collection->findOne(array('_id' => $class->getDatabaseIdentifierValue($id))); + + if ( ! $result) { + return null; + } + return $this->load($documentName, $id, $result); + } + + /** + * Loads data for a document id refreshing and overriding any local values + * if the document already exists in the identity map. + * + * @param string $documentName The document name to load. + * @param string $id The id of the document being loaded. + * @param string $data The data to load into the document. + * @return object $document The loaded document. + */ + public function load($documentName, $id, $data) + { + if ($data !== null) { + $hints = array(Query::HINT_REFRESH => Query::HINT_REFRESH); + $document = $this->_unitOfWork->getOrCreateDocument($documentName, $data, $hints); + $this->getUnitOfWork()->registerManaged($document, $id, $data); + return $document; + } + return false; + } + + /** + * Flushes all changes to objects that have been queued up to now to the database. + * This effectively synchronizes the in-memory state of managed objects with the + * database. + */ + public function flush() + { + $this->_errorIfClosed(); + $this->_unitOfWork->commit(); + } + + public function ensureDocumentIndexes($class) + { + if ($indexes = $class->getIndexes()) { + $collection = $this->getDocumentCollection($class->name); + foreach ($indexes as $index) { + $collection->ensureIndex($index['keys'], $index['options']); + } + } + } + + public function deleteDocumentIndexes($documentName) + { + return $this->getDocumentCollection($documentName)->deleteIndexes(); + } + + public function mapReduce($documentName, $map, $reduce, array $query = array(), array $options = array()) + { + $class = $this->getClassMetadata($documentName); + $db = $this->getDocumentDB($documentName); + if (is_string($map)) { + $map = new \MongoCode($map); + } + if (is_string($reduce)) { + $reduce = new \MongoCode($reduce); + } + $command = array( + 'mapreduce' => $class->getCollection(), + 'map' => $map, + 'reduce' => $reduce, + 'query' => $query + ); + $command = array_merge($command, $options); + $result = $db->command($command); + if ( ! $result['ok']) { + throw new \RuntimeException($result['errmsg']); + } + $cursor = $db->selectCollection($result['result'])->find(); + $cursor = new MongoCursor($this, $this->_hydrator, $class, $cursor); + $cursor->hydrate(false); + return $cursor; + } + + /** + * Gets a reference to the document identified by the given type and identifier + * without actually loading it. + * + * If partial objects are allowed, this method will return a partial object that only + * has its identifier populated. Otherwise a proxy is returned that automatically + * loads itself on first access. + * + * @return object The document reference. + */ + public function getReference($documentName, $identifier) + { + $class = $this->_metadataFactory->getMetadataFor($documentName); + + // Check identity map first, if its already in there just return it. + if ($document = $this->_unitOfWork->tryGetById($identifier, $class->rootDocumentName)) { + return $document; + } + $document = $this->_proxyFactory->getProxy($class->name, $identifier); + $this->_unitOfWork->registerManaged($document, $identifier, array()); + + return $document; + } + + /** + * Find a single document by its identifier or multiple by a given criteria. + * + * @param string $documentName The document to find. + * @param mixed $query A single identifier or an array of criteria. + * @param array $select The fields to select. + * @return Doctrine\ODM\MongoDB\MongoCursor $cursor + * @return object $document + */ + public function find($documentName, $query = array(), array $select = array()) + { + return $this->getRepository($documentName)->find($query, $select); + } + + /** + * Find a single document with the given query and select fields. + * + * @param string $documentName The document to find. + * @param array $query The query criteria. + * @param array $select The fields to select + * @return object $document + */ + public function findOne($documentName, array $query = array(), array $select = array()) + { + return $this->getRepository($documentName)->findOne($query, $select); + } + + /** + * Clears the DocumentManager. All documents that are currently managed + * by this DocumentManager become detached. + * + * @param string $documentName + */ + public function clear() + { + $this->_unitOfWork->clear(); + } + + /** + * Closes the DocumentManager. All documents that are currently managed + * by this DocumentManager become detached. The DocumentManager may no longer + * be used after it is closed. + */ + public function close() + { + $this->clear(); + $this->_closed = true; + } + + /** + * Throws an exception if the DocumentManager is closed or currently not active. + * + * @throws ORMException If the DocumentManager is closed. + */ + private function _errorIfClosed() + { + if ($this->_closed) { + throw MongoDBException::documentManagerClosed(); + } + } + + public function formatDBName($dbName) + { + return sprintf('%s%s%s', + $this->_config->getDBPrefix(), + $dbName, + $this->_config->getDBSuffix() + ); + } +} + diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/DocumentRepository.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/DocumentRepository.php new file mode 100644 index 00000000000..8874d6c0af7 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/DocumentRepository.php @@ -0,0 +1,210 @@ +. + */ + +namespace Doctrine\ODM\MongoDB; + +/** + * An DocumentRepository serves as a repository for documents with generic as well as + * business specific methods for retrieving documents. + * + * This class is designed for inheritance and users can subclass this class to + * write their own repositories with business-specific methods to locate documents. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class DocumentRepository +{ + /** + * @var string + */ + protected $_documentName; + + /** + * @var DocumentManager + */ + protected $_dm; + + /** + * @var Doctrine\ODM\MongoDB\Mapping\ClassMetadata + */ + protected $_class; + + /** + * Initializes a new DocumentRepository. + * + * @param DocumentManager $dm The DocumentManager to use. + * @param ClassMetadata $classMetadata The class descriptor. + */ + public function __construct($dm, Mapping\ClassMetadata $class) + { + $this->_documentName = $class->name; + $this->_dm = $dm; + $this->_class = $class; + } + + /** + * Create a new QueryBuilder instance that is prepopulated for this document name + * + * @return QueryBuilder $qb + */ + public function createQuery() + { + return $this->_dm->createQuery() + ->from($this->_documentName); + } + + /** + * Clears the repository, causing all managed documents to become detached. + */ + public function clear() + { + $this->_dm->clear($this->_class->rootDocumentName); + } + + /** + * Find a single document by its identifier or multiple by a given criteria. + * + * @param mixed $query A single identifier or an array of criteria. + * @param array $select The fields to select. + * @return Doctrine\ODM\MongoDB\MongoCursor $cursor + * @return object $document + */ + public function find($query = array(), array $select = array()) + { + if (is_string($query)) { + if ($document = $this->_dm->getUnitOfWork()->tryGetById($query, $this->_documentName)) { + return $document; // Hit! + } + + return $this->_dm->getUnitOfWork()->getDocumentPersister($this->_documentName)->loadById($query); + } else { + return $this->_dm->getUnitOfWork()->getDocumentPersister($this->_documentName)->loadAll($query, $select); + } + } + + /** + * Find a single document with the given query and select fields. + * + * @param string $documentName The document to find. + * @param array $query The query criteria. + * @param array $select The fields to select + * @return object $document + */ + public function findOne(array $query = array(), array $select = array()) + { + return $this->_dm->getUnitOfWork()->getDocumentPersister($this->_documentName)->load($query, $select); + } + + /** + * Finds all documents in the repository. + * + * @param int $hydrationMode + * @return array The documents. + */ + public function findAll() + { + return $this->find(); + } + + /** + * Finds documents by a set of criteria. + * + * @param array $criteria + * @return array + */ + public function findBy(array $criteria) + { + return $this->find($criteria); + } + + /** + * Finds a single document by a set of criteria. + * + * @param array $criteria + * @return object + */ + public function findOneBy(array $criteria) + { + return $this->findOne($criteria); + } + + /** + * Adds support for magic finders. + * + * @return array|object The found document/documents. + * @throws BadMethodCallException If the method called is an invalid find* method + * or no find* method at all and therefore an invalid + * method call. + */ + public function __call($method, $arguments) + { + if (substr($method, 0, 6) == 'findBy') { + $by = substr($method, 6, strlen($method)); + $method = 'findBy'; + } elseif (substr($method, 0, 9) == 'findOneBy') { + $by = substr($method, 9, strlen($method)); + $method = 'findOneBy'; + } else { + throw new \BadMethodCallException( + "Undefined method '$method'. The method name must start with ". + "either findBy or findOneBy!" + ); + } + + if ( ! isset($arguments[0])) { + throw ORMException::findByRequiresParameter($method.$by); + } + + $fieldName = lcfirst(\Doctrine\Common\Util\Inflector::classify($by)); + + if ($this->_class->hasField($fieldName)) { + return $this->$method(array($fieldName => $arguments[0])); + } else { + throw MongoDBException::invalidFindByCall($this->_documentName, $fieldName, $method.$by); + } + } + + /** + * @return string + */ + public function getDocumentName() + { + return $this->_documentName; + } + + /** + * @return DocumentManager + */ + public function getDocumentManager() + { + return $this->_dm; + } + + /** + * @return Mapping\ClassMetadata + */ + public function getClassMetadata() + { + return $this->_class; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Event/CollectionEventArgs.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Event/CollectionEventArgs.php new file mode 100644 index 00000000000..bf64b6962d7 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Event/CollectionEventArgs.php @@ -0,0 +1,52 @@ +. +*/ + +namespace Doctrine\ODM\MongoDB\Event; + +use Doctrine\Common\EventArgs; + +/** + * Collection event args + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 1.0 + * @author Jonathan H. Wage + */ +class CollectionEventArgs extends EventArgs +{ + private $_invoker; + private $_data; + + public function __construct($invoker, &$data) + { + $this->_invoker = $invoker; + $this->_data = $data; + } + + public function getInvoker() + { + return $this->_invoker; + } + + public function getData() + { + return $this->_data; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Event/CollectionUpdateEventArgs.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Event/CollectionUpdateEventArgs.php new file mode 100644 index 00000000000..7f15ece7c04 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Event/CollectionUpdateEventArgs.php @@ -0,0 +1,58 @@ +. +*/ + +namespace Doctrine\ODM\MongoDB\Event; + +use Doctrine\Common\EventArgs; + +/** + * Collection update event args + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 1.0 + * @author Jonathan H. Wage + */ +class CollectionUpdateEventArgs extends CollectionEventArgs +{ + private $_invoker; + private $_data; + + public function __construct($invoker, &$criteria, &$newObj) + { + $this->_invoker = $invoker; + $this->_criteria = $criteria; + $this->_newObj = $newObj; + } + + public function getInvoker() + { + return $this->_invoker; + } + + public function getCriteria() + { + return $this->_criteria; + } + + public function getNewObj() + { + return $this->_newObj; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Event/LifecycleEventArgs.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Event/LifecycleEventArgs.php new file mode 100644 index 00000000000..88fcd551be9 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Event/LifecycleEventArgs.php @@ -0,0 +1,64 @@ +. +*/ + +namespace Doctrine\ODM\MongoDB\Event; + +use Doctrine\Common\EventArgs; + +/** + * Lifecycle Events are triggered by the UnitOfWork during lifecycle transitions + * of documents. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class LifecycleEventArgs extends EventArgs +{ + /** + * @var DocumentManager + */ + private $_dm; + + /** + * @var object + */ + private $_document; + + public function __construct($document, $em) + { + $this->_document = $document; + $this->_dm = $em; + } + + public function getDocument() + { + return $this->_document; + } + + /** + * @return DocumentManager + */ + public function getDocumentManager() + { + return $this->_dm; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Event/LoadClassMetadataEventArgs.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Event/LoadClassMetadataEventArgs.php new file mode 100644 index 00000000000..db88a4d5631 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Event/LoadClassMetadataEventArgs.php @@ -0,0 +1,47 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Event; + +use Doctrine\Common\EventArgs, + Doctrine\ODM\MongoDB\Mapping\ClassMetadata; + +/** + * Class that holds event arguments for a loadMetadata event. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class LoadClassMetadataEventArgs extends EventArgs +{ + private $_classMetadata; + + public function __construct(ClassMetadata $classMetadata) + { + $this->_classMetadata = $classMetadata; + } + + public function getClassMetadata() + { + return $this->_classMetadata; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Event/OnFlushEventArgs.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Event/OnFlushEventArgs.php new file mode 100644 index 00000000000..d7760ce18c7 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Event/OnFlushEventArgs.php @@ -0,0 +1,54 @@ +. +*/ + +namespace Doctrine\ODM\MongoDB\Event; + +use Doctrine\Common\EventArgs; + +/** + * Provides event arguments for the preFlush event. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class OnFlushEventArgs extends EventArgs +{ + /** + * @var DocumentManager + */ + private $_dm; + + public function __construct($dm) + { + $this->_dm = $dm; + } + + /** + * @return DocumentManager + */ + public function getDocumentManager() + { + return $this->_dm; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Event/OnUpdatePreparedArgs.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Event/OnUpdatePreparedArgs.php new file mode 100755 index 00000000000..db857f2db26 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Event/OnUpdatePreparedArgs.php @@ -0,0 +1,59 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Event; + +use Doctrine\ODM\MongoDB\DocumentManager; + +/** + * Class that holds event arguments for a onUpdatePrepared event. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 1.0 + * @author Bulat Shakirzyanov + */ +class OnUpdatePreparedArgs extends LifecycleEventArgs +{ + private $_dm; + private $_document; + private $_update; + + public function __construct(DocumentManager $dm, $document, array &$update) + { + $this->_dm = $dm; + $this->_document = $document; + $this->_update = $update; + } + + public function getDocumentManager() + { + return $this->_dm; + } + + public function getDocument() + { + return $this->_document; + } + + public function &getUpdate() + { + return $this->_update; + } +} diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Event/PreUpdateEventArgs.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Event/PreUpdateEventArgs.php new file mode 100644 index 00000000000..9e224f8787c --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Event/PreUpdateEventArgs.php @@ -0,0 +1,115 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Event; + +use Doctrine\ODM\MongoDB\DocumentManager; + +/** + * Class that holds event arguments for a preInsert/preUpdate event. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class PreUpdateEventArgs extends LifecycleEventArgs +{ + /** + * @var array + */ + private $_documentChangeSet; + + /** + * + * @param object $document + * @param DocumentManager $dm + * @param array $changeSet + */ + public function __construct($document, $dm, array &$changeSet) + { + parent::__construct($document, $dm); + $this->_documentChangeSet = &$changeSet; + } + + public function getDocumentChangeSet() + { + return $this->_documentChangeSet; + } + + /** + * Field has a changeset? + * + * @return bool + */ + public function hasChangedField($field) + { + return isset($this->_documentChangeSet[$field]); + } + + /** + * Get the old value of the changeset of the changed field. + * + * @param string $field + * @return mixed + */ + public function getOldValue($field) + { + $this->_assertValidField($field); + + return $this->_documentChangeSet[$field][0]; + } + + /** + * Get the new value of the changeset of the changed field. + * + * @param string $field + * @return mixed + */ + public function getNewValue($field) + { + $this->_assertValidField($field); + + return $this->_documentChangeSet[$field][1]; + } + + /** + * Set the new value of this field. + * + * @param string $field + * @param mixed $value + */ + public function setNewValue($field, $value) + { + $this->_assertValidField($field); + + $this->_documentChangeSet[$field][1] = $value; + } + + private function _assertValidField($field) + { + if ( ! isset($this->_documentChangeSet[$field])) { + throw new \InvalidArgumentException( + "Field '".$field."' is not a valid field of the document ". + "'".get_class($this->getDocument())."' in PreInsertUpdateEventArgs." + ); + } + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Hydrator.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Hydrator.php new file mode 100644 index 00000000000..11b9ddf9719 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Hydrator.php @@ -0,0 +1,151 @@ +. + */ + +namespace Doctrine\ODM\MongoDB; + +use Doctrine\ODM\MongoDB\Query, + Doctrine\ODM\MongoDB\Mapping\ClassMetadata, + Doctrine\ODM\MongoDB\PersistentCollection, + Doctrine\ODM\MongoDB\Mapping\Types\Type, + Doctrine\Common\Collections\ArrayCollection, + Doctrine\Common\Collections\Collection; + +/** + * The Hydrator class is responsible for converting a document from MongoDB + * which is an array to classes and collections based on the mapping of the document + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 1.0 + * @author Jonathan H. Wage + */ +class Hydrator +{ + /** + * The DocumentManager associationed with this Hydrator + * + * @var Doctrine\ODM\MongoDB\DocumentManager + */ + private $_dm; + + /** + * Mongo command prefix + * @var string + */ + private $_cmd; + + /** + * Create a new Hydrator instance + * + * @param Doctrine\ODM\MongoDB\DocumentManager $dm + */ + public function __construct(DocumentManager $dm) + { + $this->_dm = $dm; + $this->_cmd = $dm->getConfiguration()->getMongoCmd(); + } + + /** + * Hydrate array of MongoDB document data into the given document object + * based on the mapping information provided in the ClassMetadata instance. + * + * @param ClassMetadata $metadata The ClassMetadata instance for mapping information. + * @param string $document The document object to hydrate the data into. + * @param array $data The array of document data. + * @return array $values The array of hydrated values. + */ + public function hydrate(ClassMetadata $metadata, $document, $data) + { + $values = array(); + foreach ($metadata->fieldMappings as $mapping) { + $rawValue = $this->_getFieldValue($mapping, $document, $data); + if ( ! isset($rawValue)) { + continue; + } + + if (isset($mapping['embedded'])) { + $embeddedMetadata = $this->_dm->getClassMetadata($mapping['targetDocument']); + $embeddedDocument = $embeddedMetadata->newInstance(); + if ($mapping['type'] === 'many') { + $documents = new ArrayCollection(); + foreach ($rawValue as $docArray) { + $doc = clone $embeddedDocument; + $this->hydrate($embeddedMetadata, $doc, $docArray); + $documents->add($doc); + } + $metadata->setFieldValue($document, $mapping['fieldName'], $documents); + $value = $documents; + } else { + $value = clone $embeddedDocument; + $this->hydrate($embeddedMetadata, $value, $rawValue); + $metadata->setFieldValue($document, $mapping['fieldName'], $value); + } + } elseif (isset($mapping['reference'])) { + $targetMetadata = $this->_dm->getClassMetadata($mapping['targetDocument']); + $targetDocument = $targetMetadata->newInstance(); + if ($mapping['type'] === 'one' && isset($rawValue[$this->_cmd . 'id'])) { + $id = $targetMetadata->getPHPIdentifierValue($rawValue[$this->_cmd . 'id']); + $proxy = $this->_dm->getReference($mapping['targetDocument'], $id); + $metadata->setFieldValue($document, $mapping['fieldName'], $proxy); + } elseif ($mapping['type'] === 'many' && (is_array($rawValue) || $rawValue instanceof Collection)) { + $documents = new PersistentCollection($this->_dm, $targetMetadata, new ArrayCollection()); + $documents->setInitialized(false); + foreach ($rawValue as $v) { + $id = $targetMetadata->getPHPIdentifierValue($v[$this->_cmd . 'id']); + $proxy = $this->_dm->getReference($mapping['targetDocument'], $id); + $documents->add($proxy); + } + $metadata->setFieldValue($document, $mapping['fieldName'], $documents); + } + } else { + $value = Type::getType($mapping['type'])->convertToPHPValue($rawValue); + $metadata->setFieldValue($document, $mapping['fieldName'], $value); + } + if (isset($value)) { + $values[$mapping['fieldName']] = $value; + } + } + if (isset($data['_id'])) { + $metadata->setIdentifierValue($document, $data['_id']); + } + return $values; + } + + private function _getFieldValue(array $mapping, $document, $data) + { + $names = isset($mapping['alsoLoadFields']) ? $mapping['alsoLoadFields'] : array(); + array_unshift($names, $mapping['fieldName']); + foreach ($names as $name) { + if (isset($data[$name])) { + return $data[$name]; + } + } + if (isset($mapping['alsoLoadMethods'])) { + foreach ($mapping['alsoLoadMethods'] as $alsoLoad) { + $names = $alsoLoad['name']; + foreach ($names as $name) { + if (isset($data[$name])) { + $document->$alsoLoad['method']($data[$name]); + } + } + } + } + return null; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Internal/CommitOrderCalculator.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Internal/CommitOrderCalculator.php new file mode 100644 index 00000000000..8e3402c65d3 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Internal/CommitOrderCalculator.php @@ -0,0 +1,132 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Internal; + +/** + * The CommitOrderCalculator is used by the UnitOfWork to sort out the + * correct order in which changes to documents need to be persisted. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class CommitOrderCalculator +{ + const NOT_VISITED = 1; + const IN_PROGRESS = 2; + const VISITED = 3; + + private $_nodeStates = array(); + private $_classes = array(); // The nodes to sort + private $_relatedClasses = array(); + private $_sorted = array(); + + /** + * Clears the current graph. + * + * @return void + */ + public function clear() + { + $this->_classes = + $this->_relatedClasses = array(); + } + + /** + * Gets a valid commit order for all current nodes. + * + * Uses a depth-first search (DFS) to traverse the graph. + * The desired topological sorting is the reverse postorder of these searches. + * + * @return array The list of ordered classes. + */ + public function getCommitOrder() + { + // Check whether we need to do anything. 0 or 1 node is easy. + $nodeCount = count($this->_classes); + if ($nodeCount === 0) { + return array(); + } + + if ($nodeCount === 1) { + return array_values($this->_classes); + } + + // Init + foreach ($this->_classes as $node) { + $this->_nodeStates[$node->name] = self::NOT_VISITED; + } + + // Go + foreach ($this->_classes as $node) { + if ($this->_nodeStates[$node->name] == self::NOT_VISITED) { + $this->_visitNode($node); + } + } + + $sorted = array_reverse($this->_sorted); + + $this->_sorted = $this->_nodeStates = array(); + + return $sorted; + } + + private function _visitNode($node) + { + $this->_nodeStates[$node->name] = self::IN_PROGRESS; + + if (isset($this->_relatedClasses[$node->name])) { + foreach ($this->_relatedClasses[$node->name] as $relatedNode) { + if ($this->_nodeStates[$relatedNode->name] == self::NOT_VISITED) { + $this->_visitNode($relatedNode); + } + } + } + + $this->_nodeStates[$node->name] = self::VISITED; + $this->_sorted[] = $node; + } + + public function addDependency($fromClass, $toClass) + { + $this->_relatedClasses[$fromClass->name][] = $toClass; + } + + public function hasDependency($fromClass, $toClass) + { + if ( ! isset($this->_relatedClasses[$fromClass->name])) { + return false; + } + + return in_array($toClass, $this->_relatedClasses[$fromClass->name]); + } + + public function hasClass($className) + { + return isset($this->_classes[$className]); + } + + public function addClass($class) + { + $this->_classes[$class->name] = $class; + } +} diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php new file mode 100644 index 00000000000..cdcd1790c7e --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php @@ -0,0 +1,936 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping; + +/** + * A ClassMetadata instance holds all the object-document mapping metadata + * of a document and it's references. + * + * Once populated, ClassMetadata instances are usually cached in a serialized form. + * + * IMPORTANT NOTE: + * + * The fields of this class are only public for 2 reasons: + * 1) To allow fast READ access. + * 2) To drastically reduce the size of a serialized instance (private/protected members + * get the whole class name, namespace inclusive, prepended to every property in + * the serialized representation). + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class ClassMetadata +{ + /* The inheritance mapping types */ + /** + * NONE means the class does not participate in an inheritance hierarchy + * and therefore does not need an inheritance mapping type. + */ + const INHERITANCE_TYPE_NONE = 1; + + /** + * SINGLE_COLLECTION means the class will be persisted according to the rules of + * Single Collection Inheritance. + */ + const INHERITANCE_TYPE_SINGLE_COLLECTION = 2; + + /** + * COLLECTION_PER_CLASS means the class will be persisted according to the rules + * of Concrete Collection Inheritance. + */ + const INHERITANCE_TYPE_COLLECTION_PER_CLASS = 3; + + /** + * READ-ONLY: The name of the mongo database the document is mapped to. + */ + public $db; + + /** + * READ-ONLY: The name of the monge collection the document is mapped to. + */ + public $collection; + + /** + * READ-ONLY: The field name of the document identifier. + */ + public $identifier; + + /** + * READ-ONLY: The field that stores a file reference and indicates the + * document is a file and should be stored on the MongoGridFS. + */ + public $file; + + /** + * READ-ONLY: The array of indexes for the document collection. + */ + public $indexes = array(); + + /** + * READ-ONLY: The name of the document class. + */ + public $name; + + /** + * READ-ONLY: The namespace the document class is contained in. + * + * @var string + * @todo Not really needed. Usage could be localized. + */ + public $namespace; + + /** + * READ-ONLY: The name of the document class that is at the root of the mapped document inheritance + * hierarchy. If the document is not part of a mapped inheritance hierarchy this is the same + * as {@link $documentName}. + * + * @var string + */ + public $rootDocumentName; + + /** + * The name of the custom repository class used for the document class. + * (Optional). + * + * @var string + */ + public $customRepositoryClassName; + + /** + * Whether custom id value is allowed or not + * + * @var bool + */ + public $allowCustomID = false; + + /** + * READ-ONLY: The names of the parent classes (ancestors). + * + * @var array + */ + public $parentClasses = array(); + + /** + * READ-ONLY: The names of all subclasses (descendants). + * + * @var array + */ + public $subClasses = array(); + + /** + * The ReflectionProperty instances of the mapped class. + * + * @var array + */ + public $reflFields = array(); + + /** + * The prototype from which new instances of the mapped class are created. + * + * @var object + */ + private $_prototype; + + /** + * READ-ONLY: The inheritance mapping type used by the class. + * + * @var integer + */ + public $inheritanceType = self::INHERITANCE_TYPE_NONE; + + /** + * READ-ONLY: The field mappings of the class. + * Keys are field names and values are mapping definitions. + * + * The mapping definition array has the following values: + * + * - fieldName (string) + * The name of the field in the Document. + * + * - id (boolean, optional) + * Marks the field as the primary key of the document. Multiple fields of an + * document can have the id attribute, forming a composite key. + * + * @var array + */ + public $fieldMappings = array(); + + /** + * READ-ONLY: The discriminator value of this class. + * + * This does only apply to the JOINED and SINGLE_COLLECTION inheritance mapping strategies + * where a discriminator field is used. + * + * @var mixed + * @see discriminatorField + */ + public $discriminatorValue; + + /** + * READ-ONLY: The discriminator map of all mapped classes in the hierarchy. + * + * This does only apply to the SINGLE_COLLECTION inheritance mapping strategy + * where a discriminator field is used. + * + * @var mixed + * @see discriminatorField + */ + public $discriminatorMap = array(); + + /** + * READ-ONLY: The definition of the discriminator field used in SINGLE_COLLECTION + * inheritance mapping. + * + * @var string + */ + public $discriminatorField; + + /** + * The ReflectionClass instance of the mapped class. + * + * @var ReflectionClass + */ + public $reflClass; + + /** + * READ-ONLY: Whether this class describes the mapping of a mapped superclass. + * + * @var boolean + */ + public $isMappedSuperclass = false; + + /** + * READ-ONLY: Whether this class describes the mapping of a embedded document. + * + * @var boolean + */ + public $isEmbeddedDocument = false; + + /** + * Initializes a new ClassMetadata instance that will hold the object-document mapping + * metadata of the class with the given name. + * + * @param string $documentName The name of the document class the new instance is used for. + */ + public function __construct($documentName) + { + $this->name = $documentName; + $this->rootDocumentName = $documentName; + $this->reflClass = new \ReflectionClass($documentName); + $this->namespace = $this->reflClass->getNamespaceName(); + + $e = explode('\\', $documentName); + if (count($e) > 1) { + $e = array_map(function($value) { + return strtolower($value); + }, $e); + $collection = array_pop($e); + } else { + $collection = strtolower($documentName); + } + $this->setCollection($collection); + + foreach ($this->reflClass->getProperties() as $property) { + $fieldName = $property->getName(); + $mapping = array( + 'fieldName' => $fieldName + ); + $this->mapField($mapping); + } + } + + /** + * Checks whether a field is part of the identifier/primary key field(s). + * + * @param string $fieldName The field name + * @return boolean TRUE if the field is part of the table identifier/primary key field(s), + * FALSE otherwise. + */ + public function isIdentifier($fieldName) + { + return $this->identifier === $fieldName ? true : false; + } + + /** + * INTERNAL: + * Sets the mapped identifier field of this class. + * + * @param array $identifier + */ + public function setIdentifier($identifier) + { + $this->identifier = $identifier; + } + + /** + * Checks whether the class has a (mapped) field with a certain name. + * + * @return boolean + */ + public function hasField($fieldName) + { + return isset($this->fieldMappings[$fieldName]); + } + + /** + * Sets the inheritance type used by the class and it's subclasses. + * + * @param integer $type + */ + public function setInheritanceType($type) + { + $this->inheritanceType = $type; + } + + /** + * Registers a custom repository class for the document class. + * + * @param string $mapperClassName The class name of the custom mapper. + */ + public function setCustomRepositoryClass($repositoryClassName) + { + $this->customRepositoryClassName = $repositoryClassName; + } + + /** + * Dispatches the lifecycle event of the given document to the registered + * lifecycle callbacks and lifecycle listeners. + * + * @param string $event The lifecycle event. + * @param Document $document The Document on which the event occured. + */ + public function invokeLifecycleCallbacks($lifecycleEvent, $document) + { + foreach ($this->lifecycleCallbacks[$lifecycleEvent] as $callback) { + $document->$callback(); + } + } + + /** + * Whether the class has any attached lifecycle listeners or callbacks for a lifecycle event. + * + * @param string $lifecycleEvent + * @return boolean + */ + public function hasLifecycleCallbacks($lifecycleEvent) + { + return isset($this->lifecycleCallbacks[$lifecycleEvent]); + } + + /** + * Gets the registered lifecycle callbacks for an event. + * + * @param string $event + * @return array + */ + public function getLifecycleCallbacks($event) + { + return isset($this->lifecycleCallbacks[$event]) ? $this->lifecycleCallbacks[$event] : array(); + } + + /** + * Adds a lifecycle callback for documents of this class. + * + * Note: If the same callback is registered more than once, the old one + * will be overridden. + * + * @param string $callback + * @param string $event + */ + public function addLifecycleCallback($callback, $event) + { + $this->lifecycleCallbacks[$event][] = $callback; + } + + /** + * Sets the lifecycle callbacks for documents of this class. + * Any previously registered callbacks are overwritten. + * + * @param array $callbacks + */ + public function setLifecycleCallbacks(array $callbacks) + { + $this->lifecycleCallbacks = $callbacks; + } + + /** + * Sets the discriminator field name. + * + * @param string $discriminatorField + * @see getDiscriminatorField() + */ + public function setDiscriminatorField($discriminatorField) + { + if ( ! isset($discriminatorField['name']) && isset($discriminatorField['fieldName'])) { + $discriminatorField['name'] = $discriminatorField['fieldName']; + } + $this->discriminatorField = $discriminatorField; + } + + /** + * Sets the discriminator values used by this class. + * Used for JOINED and SINGLE_TABLE inheritance mapping strategies. + * + * @param array $map + */ + public function setDiscriminatorMap(array $map) + { + foreach ($map as $value => $className) { + if (strpos($className, '\\') === false && strlen($this->namespace)) { + $className = $this->namespace . '\\' . $className; + } + $this->discriminatorMap[$value] = $className; + if ($this->name == $className) { + $this->discriminatorValue = $value; + } else { + if (is_subclass_of($className, $this->name)) { + $this->subClasses[] = $className; + } + } + } + } + + /** + * Add a index for this Document. + * + * @param array $keys Array of keys for the index. + * @param array $options Array of options for the index. + */ + public function addIndex($keys, $options) + { + $this->indexes[] = array( + 'keys' => array_map(function($value) { + return strtolower($value) == 'asc' ? 1 : -1; + }, $keys), + 'options' => $options + ); + } + + /** + * Returns the array of indexes for this Document. + * + * @return array $indexes The array of indexes. + */ + public function getIndexes() + { + return $this->indexes; + } + + /** + * Gets the ReflectionClass instance of the mapped class. + * + * @return ReflectionClass + */ + public function getReflectionClass() + { + return $this->reflClass; + } + + /** + * Gets the ReflectionPropertys of the mapped class. + * + * @return array An array of ReflectionProperty instances. + */ + public function getReflectionProperties() + { + return $this->reflFields; + } + + /** + * Gets a ReflectionProperty for a specific field of the mapped class. + * + * @param string $name + * @return ReflectionProperty + */ + public function getReflectionProperty($name) + { + return $this->reflFields[$name]; + } + + /** + * The name of this Document class. + * + * @return string $name The Document class name. + */ + public function getName() + { + return $this->name; + } + + /** + * The namespace this Document class belongs to. + * + * @return string $namespace The namespace name. + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * Returns the database this Document is mapped to. + * + * @return string $db The database name. + */ + public function getDB() + { + return $this->db; + } + + /** + * Set the database this Document is mapped to. + * + * @param string $db The database name + */ + public function setDB($db) + { + $this->db = $db; + } + + /** + * Get the collection this Document is mapped to. + * + * @return string $collection The collection name. + */ + public function getCollection() + { + return $this->collection; + } + + /** + * Sets the collection this Document is mapped to. + * + * @param string $collection The collection name. + */ + public function setCollection($collection) + { + $this->collection = $collection; + } + + /** + * Reeturns TRUE if this Document is mapped to a collection FALSE otherwise. + * + * @return boolean + */ + public function isMappedToCollection() + { + return $this->collection ? true : false; + } + + /** + * Returns TRUE if this Document is a file to be stored on the MongoGridFS FALSE otherwise. + * + * @return boolean + */ + public function isFile() + { + return $this->file ? true :false; + } + + /** + * Returns the file field name. + * + * @return string $file The file field name. + */ + public function getFile() + { + return $this->file; + } + + /** + * Map a field. + * + * @param array $mapping The mapping information. + */ + public function mapField(array $mapping) + { + if (isset($mapping['name'])) { + $mapping['fieldName'] = $mapping['name']; + } + $mapping['name'] = $mapping['fieldName']; + + // unset transient fields + if (isset($mapping['transient']) && $mapping['transient']) { + unset($this->fieldMappings[$mapping['fieldName']]); + return; + } + + if ($mapping['fieldName'] === 'id') { + $mapping['id'] = true; + $mapping['type'] = isset($mapping['type']) ? $mapping['type'] : 'id'; + } + + if ( ! isset($mapping['type'])) { + $mapping['type'] = 'string'; + } + + if (isset($mapping['targetDocument']) && strpos($mapping['targetDocument'], '\\') === false && strlen($this->namespace)) { + $mapping['targetDocument'] = $this->namespace . '\\' . $mapping['targetDocument']; + } + + if ($this->reflClass->hasProperty($mapping['fieldName'])) { + $reflProp = $this->reflClass->getProperty($mapping['fieldName']); + $reflProp->setAccessible(true); + $this->reflFields[$mapping['fieldName']] = $reflProp; + } + + if (isset($mapping['cascade']) && in_array('all', (array) $mapping['cascade'])) { + unset($mapping['all']); + $default = true; + } else { + $default = false; + } + $mapping['isCascadeRemove'] = $default; + $mapping['isCascadePersist'] = $default; + $mapping['isCascadeRefresh'] = $default; + $mapping['isCascadeMerge'] = $default; + $mapping['isCascadeDetach'] = $default; + if (isset($mapping['cascade']) && is_array($mapping['cascade'])) { + foreach ($mapping['cascade'] as $cascade) { + $mapping['isCascade' . ucfirst($cascade)] = true; + } + } + if (isset($mapping['file']) && $mapping['file'] === true) { + $this->file = $mapping['fieldName']; + } + if (isset($mapping['id']) && $mapping['id'] === true) { + $this->identifier = $mapping['fieldName']; + } + if ( ! isset($mapping['nullable'])) { + $mapping['nullable'] = false; + } + $this->fieldMappings[$mapping['fieldName']] = $mapping; + } + + /** + * Map a MongoGridFSFile. + * + * @param array $mapping The mapping information. + */ + public function mapFile(array $mapping) + { + $mapping['file'] = true; + $mapping['type'] = 'file'; + $this->mapField($mapping); + } + + /** + * Map a single embedded document. + * + * @param array $mapping The mapping information. + */ + public function mapOneEmbedded(array $mapping) + { + $mapping['embedded'] = true; + $mapping['type'] = 'one'; + $this->mapField($mapping); + } + + /** + * Map a collection of embedded documents. + * + * @param array $mapping The mapping information. + */ + public function mapManyEmbedded(array $mapping) + { + $mapping['embedded'] = true; + $mapping['type'] = 'many'; + $this->mapField($mapping); + } + + /** + * Map a single document reference. + * + * @param array $mapping The mapping information. + */ + public function mapOneReference(array $mapping) + { + $mapping['reference'] = true; + $mapping['type'] = 'one'; + $this->mapField($mapping); + } + + /** + * Map a collection of document references. + * + * @param array $mapping The mapping information. + */ + public function mapManyReference(array $mapping) + { + $mapping['reference'] = true; + $mapping['type'] = 'many'; + $this->mapField($mapping); + } + + /** + * Checks whether the class has a mapped association with the given field name. + * + * @param string $fieldName + * @return boolean + */ + public function hasReference($fieldName) + { + return isset($this->fieldMappings[$fieldName]['reference']); + } + + /** + * Checks whether the class has a mapped association for the specified field + * and if yes, checks whether it is a single-valued association (to-one). + * + * @param string $fieldName + * @return boolean TRUE if the association exists and is single-valued, FALSE otherwise. + */ + public function isSingleValuedReference($fieldName) + { + return isset($this->fieldMappings[$fieldName]['reference']) && + $this->fieldMappings[$fieldName]['type'] === 'one'; + } + + /** + * Checks whether the class has a mapped association for the specified field + * and if yes, checks whether it is a collection-valued association (to-many). + * + * @param string $fieldName + * @return boolean TRUE if the association exists and is collection-valued, FALSE otherwise. + */ + public function isCollectionValuedReference($fieldName) + { + return isset($this->fieldMappings[$fieldName]['reference']) && + $this->fieldMappings[$fieldName]['type'] === 'many'; + } + + public function getPHPIdentifierValue($id) + { + $idType = $this->fieldMappings[$this->identifier]['type']; + return Types\Type::getType($idType)->convertToPHPValue($id); + } + + public function getDatabaseIdentifierValue($id) + { + $idType = $this->fieldMappings[$this->identifier]['type']; + return Types\Type::getType($idType)->convertToDatabaseValue($id); + } + + /** + * Sets the document identifier of a document. + * + * @param object $document + * @param mixed $id + */ + public function setIdentifierValue($document, $id) + { + $id = $this->getPHPIdentifierValue($id); + if (isset($this->reflFields[$this->identifier])) { + $this->reflFields[$this->identifier]->setValue($document, $id); + } else { + $identifier = $this->identifier; + $document->$identifier = $id; + } + } + + /** + * Gets the document identifier. + * + * @param object $document + * @return string $id + */ + public function getIdentifierValue($document) + { + if (isset($this->reflFields[$this->identifier])) { + return (string) $this->reflFields[$this->identifier]->getValue($document); + } else { + $identifier = $this->identifier; + return isset($document->$identifier) ? (string) $document->identifier : null; + } + } + + /** + * Get the document identifier object. + * + * @param string $document + * @return MongoId $id The MongoID object. + */ + public function getIdentifierObject($document) + { + if ($id = $this->getIdentifierValue($document)) { + return $this->getDatabaseIdentifierValue($id); + } + } + + /** + * Sets the specified field to the specified value on the given document. + * + * @param object $document + * @param string $field + * @param mixed $value + */ + public function setFieldValue($document, $field, $value) + { + if (isset($this->reflFields[$field])) { + $this->reflFields[$field]->setValue($document, $value); + } else { + $document->$field = $value; + } + } + + /** + * Gets the specified field's value off the given document. + * + * @param object $document + * @param string $field + */ + public function getFieldValue($document, $field) + { + if (isset($this->reflFields[$field])) { + return $this->reflFields[$field]->getValue($document); + } else { + return isset($document->$field) ? $document->$field : null; + } + } + + /** + * @return boolean + */ + public function isInheritanceTypeNone() + { + return $this->inheritanceType == self::INHERITANCE_TYPE_NONE; + } + + /** + * Checks whether the mapped class uses the SINGLE_COLLECTION inheritance mapping strategy. + * + * @return boolean TRUE if the class participates in a SINGLE_COLLECTION inheritance mapping, + * FALSE otherwise. + */ + public function isInheritanceTypeSingleCollection() + { + return $this->inheritanceType == self::INHERITANCE_TYPE_SINGLE_COLLECTION; + } + + /** + * Checks whether the mapped class uses the COLLECTION_PER_CLASS inheritance mapping strategy. + * + * @return boolean TRUE if the class participates in a COLLECTION_PER_CLASS inheritance mapping, + * FALSE otherwise. + */ + public function isInheritanceTypeCollectionPerClass() + { + return $this->inheritanceType == self::INHERITANCE_TYPE_COLLECTION_PER_CLASS; + } + + /** + * Sets the parent class names. + * Assumes that the class names in the passed array are in the order: + * directParent -> directParentParent -> directParentParentParent ... -> root. + */ + public function setParentClasses(array $classNames) + { + $this->parentClasses = $classNames; + if (count($classNames) > 0) { + $this->rootDocumentName = array_pop($classNames); + } + } + + public function setAllowCustomId($boolean) + { + $this->allowCustomID = (bool) $boolean; + } + + public function getAllowCustomID() + { + return $this->allowCustomID; + } + + /** + * Creates a new instance of the mapped class, without invoking the constructor. + * + * @return object + */ + public function newInstance() + { + if ($this->_prototype === null) { + $this->_prototype = unserialize(sprintf('O:%d:"%s":0:{}', strlen($this->name), $this->name)); + } + return clone $this->_prototype; + } + + /** + * Determines which fields get serialized. + * + * It is only serialized what is necessary for best unserialization performance. + * That means any metadata properties that are not set or empty or simply have + * their default value are NOT serialized. + * + * Parts that are also NOT serialized because they can not be properly unserialized: + * - reflClass (ReflectionClass) + * - reflFields (ReflectionProperty array) + * + * @return array The names of all the fields that should be serialized. + */ + public function __sleep() + { + // This metadata is always serialized/cached. + $serialized = array( + 'fieldMappings', + 'identifier', + 'name', + 'namespace', // TODO: REMOVE + 'db', + 'collection', + 'rootDocumentName', + 'allowCustomID', + ); + + if ($this->inheritanceType != self::INHERITANCE_TYPE_NONE) { + $serialized[] = 'inheritanceType'; + $serialized[] = 'discriminatorField'; + $serialized[] = 'discriminatorValue'; + $serialized[] = 'discriminatorMap'; + $serialized[] = 'parentClasses'; + $serialized[] = 'subClasses'; + } + + if ($this->isMappedSuperclass) { + $serialized[] = 'isMappedSuperclass'; + } + + return $serialized; + + } + + /** + * Restores some state that can not be serialized/unserialized. + * + * @return void + */ + public function __wakeup() + { + $this->reflClass = new \ReflectionClass($this->name); + + foreach ($this->fieldMappings as $field => $mapping) { + $reflField = $this->reflClass->getProperty($field); + $reflField->setAccessible(true); + $this->reflFields[$field] = $reflField; + } + } +} diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php new file mode 100644 index 00000000000..875ee635fb3 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php @@ -0,0 +1,306 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping; + +use Doctrine\ODM\MongoDB\DocumentManager, + Doctrine\ODM\MongoDB\Mapping\ClassMetadata, + Doctrine\ODM\MongoDB\MongoDBException, + Doctrine\ODM\MongoDB\ODMEvents, + Doctrine\Common\Cache\Cache; + +/** + * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the + * metadata mapping informations of a class which describes how a class should be mapped + * to a document database. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class ClassMetadataFactory +{ + /** The DocumentManager instance */ + private $_dm; + + /** The array of loaded ClassMetadata instances */ + private $_loadedMetadata; + + /** The used metadata driver. */ + private $_driver; + + /** The event manager instance */ + private $_evm; + + /** The used cache driver. */ + private $_cacheDriver; + + /** Whether factory has been lazily initialized yet */ + private $_initialized = false; + + /** + * Creates a new factory instance that uses the given DocumentManager instance. + * + * @param $dm The DocumentManager instance + */ + public function __construct(DocumentManager $dm) + { + $this->_dm = $dm; + } + + /** + * Lazy initialization of this stuff, especially the metadata driver, + * since these are not needed at all when a metadata cache is active. + */ + private function _initialize() + { + $this->_driver = $this->_dm->getConfiguration()->getMetadataDriverImpl(); + $this->_evm = $this->_dm->getEventManager(); + $this->_initialized = true; + } + + /** + * Sets the cache driver used by the factory to cache ClassMetadata instances. + * + * @param Doctrine\Common\Cache\Cache $cacheDriver + */ + public function setCacheDriver($cacheDriver) + { + $this->_cacheDriver = $cacheDriver; + } + + /** + * Gets the cache driver used by the factory to cache ClassMetadata instances. + * + * @return Doctrine\Common\Cache\Cache + */ + public function getCacheDriver() + { + return $this->_cacheDriver; + } + + /** + * Gets the array of loaded ClassMetadata instances. + * + * @return array $loadedMetadata The loaded metadata. + */ + public function getLoadedMetadata() + { + return $this->_loadedMetadata; + } + + /** + * Gets the class metadata descriptor for a class. + * + * @param string $className The name of the class. + * @return Doctrine\ODM\MongoDB\Mapping\ClassMetadata + */ + public function getMetadataFor($className) + { + if ( ! isset($this->_loadedMetadata[$className])) { + $realClassName = $className; + + // Check for namespace alias + if (strpos($className, ':') !== false) { + list($namespaceAlias, $simpleClassName) = explode(':', $className); + $realClassName = $this->_dm->getConfiguration()->getDocumentNamespace($namespaceAlias) . '\\' . $simpleClassName; + + if (isset($this->_loadedMetadata[$realClassName])) { + // We do not have the alias name in the map, include it + $this->_loadedMetadata[$className] = $this->_loadedMetadata[$realClassName]; + + return $this->_loadedMetadata[$realClassName]; + } + } + + if ($this->_cacheDriver) { + if (($cached = $this->_cacheDriver->fetch("$realClassName\$MONGODBODMCLASSMETADATA")) !== false) { + $this->_loadedMetadata[$realClassName] = $cached; + } else { + foreach ($this->_loadMetadata($realClassName) as $loadedClassName) { + $this->_cacheDriver->save( + "$loadedClassName\$MONGODBODMCLASSMETADATA", $this->_loadedMetadata[$loadedClassName], null + ); + } + } + } else { + $this->_loadMetadata($realClassName); + } + + if ($className != $realClassName) { + // We do not have the alias name in the map, include it + $this->_loadedMetadata[$className] = $this->_loadedMetadata[$realClassName]; + } + } + + return $this->_loadedMetadata[$className]; + } + + /** + * Loads the metadata of the class in question and all it's ancestors whose metadata + * is still not loaded. + * + * @param string $name The name of the class for which the metadata should get loaded. + * @param array $tables The metadata collection to which the loaded metadata is added. + */ + private function _loadMetadata($className) + { + if ( ! $this->_initialized) { + $this->_initialize(); + } + + $loaded = array(); + + $parentClasses = $this->_getParentClasses($className); + $parentClasses[] = $className; + + // Move down the hierarchy of parent classes, starting from the topmost class + $parent = null; + $visited = array(); + foreach ($parentClasses as $className) { + if (isset($this->_loadedMetadata[$className])) { + $parent = $this->_loadedMetadata[$className]; + if ( ! $parent->isMappedSuperclass) { + array_unshift($visited, $className); + } + continue; + } + + $class = $this->_newClassMetadataInstance($className); + + if ($parent) { + $class->setInheritanceType($parent->inheritanceType); + $class->setDiscriminatorField($parent->discriminatorField); + $this->_addInheritedFields($class, $parent); + $class->setIdentifier($parent->identifier); + $class->setDiscriminatorMap($parent->discriminatorMap); + } + + $this->_driver->loadMetadataForClass($className, $class); + + if ($parent && $parent->isInheritanceTypeSingleCollection()) { + $class->setDB($parent->getDB()); + $class->setCollection($parent->getCollection()); + } + + if ( ! $class->identifier) { + $class->mapField(array( + 'id' => true, + 'fieldName' => 'id' + )); + } + $db = $class->getDB() ?: $this->_dm->getConfiguration()->getDefaultDB(); + $class->setDB($this->_dm->formatDBName($db)); + + $class->setParentClasses($visited); + + if ($this->_evm->hasListeners(ODMEvents::loadClassMetadata)) { + $eventArgs = new \Doctrine\ODM\MongoDB\Event\LoadClassMetadataEventArgs($class); + $this->_evm->dispatchEvent(ODMEvents::loadClassMetadata, $eventArgs); + } + + $this->_loadedMetadata[$className] = $class; + + $parent = $class; + + if ( ! $class->isMappedSuperclass) { + array_unshift($visited, $className); + } + + $loaded[] = $className; + } + + return $loaded; + } + + /** + * Checks whether the factory has the metadata for a class loaded already. + * + * @param string $className + * @return boolean TRUE if the metadata of the class in question is already loaded, FALSE otherwise. + */ + public function hasMetadataFor($className) + { + return isset($this->_loadedMetadata[$className]); + } + + /** + * Sets the metadata descriptor for a specific class. + * + * NOTE: This is only useful in very special cases, like when generating proxy classes. + * + * @param string $className + * @param ClassMetadata $class + */ + public function setMetadataFor($className, $class) + { + $this->_loadedMetadata[$className] = $class; + } + + /** + * Creates a new ClassMetadata instance for the given class name. + * + * @param string $className + * @return Doctrine\ODM\MongoDB\Mapping\ClassMetadata + */ + protected function _newClassMetadataInstance($className) + { + return new ClassMetadata($className); + } + + /** + * Get array of parent classes for the given document class + * + * @param string $name + * @return array $parentClasses + */ + protected function _getParentClasses($name) + { + // Collect parent classes, ignoring transient (not-mapped) classes. + $parentClasses = array(); + foreach (array_reverse(class_parents($name)) as $parentClass) { + $parentClasses[] = $parentClass; + } + return $parentClasses; + } + + /** + * Adds inherited fields to the subclass mapping. + * + * @param Doctrine\ODM\MongoDB\Mapping\ClassMetadata $subClass + * @param Doctrine\ODM\MongoDB\Mapping\ClassMetadata $parentClass + */ + private function _addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass) + { + foreach ($parentClass->fieldMappings as $fieldName => $mapping) { + if ( ! isset($mapping['inherited'])) { + $mapping['inherited'] = $parentClass->name; + } + if ( ! isset($mapping['declared'])) { + $mapping['declared'] = $parentClass->name; + } + $subClass->mapField($mapping); + } + foreach ($parentClass->reflFields as $name => $field) { + $subClass->reflFields[$name] = $field; + } + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AbstractFileDriver.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AbstractFileDriver.php new file mode 100644 index 00000000000..ebb2c81d5c5 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AbstractFileDriver.php @@ -0,0 +1,151 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping\Driver; + +use Doctrine\ODM\MongoDB\MongoDBException; + +/** + * Base driver for file-based metadata drivers. + * + * A file driver operates in a mode where it loads the mapping files of individual + * classes on demand. This requires the user to adhere to the convention of 1 mapping + * file per class and the file names of the mapping files must correspond to the full + * class name, including namespace, with the namespace delimiters '\', replaced by dots '.'. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +abstract class AbstractFileDriver implements Driver +{ + /** + * The paths where to look for mapping files. + * + * @var array + */ + protected $_paths = array(); + + /** + * The file extension of mapping documents. + * + * @var string + */ + protected $_fileExtension; + + /** + * Initializes a new FileDriver that looks in the given path(s) for mapping + * documents and operates in the specified operating mode. + * + * @param string|array $paths One or multiple paths where mapping documents can be found. + */ + public function __construct($paths) + { + $this->addPaths((array) $paths); + } + + /** + * Append lookup paths to metadata driver. + * + * @param array $paths + */ + public function addPaths(array $paths) + { + $this->_paths = array_unique(array_merge($this->_paths, $paths)); + } + + /** + * Retrieve the defined metadata lookup paths. + * + * @return array + */ + public function getPaths() + { + return $this->_paths; + } + + /** + * Get the file extension used to look for mapping files under + * + * @return void + */ + public function getFileExtension() + { + return $this->_fileExtension; + } + + /** + * Set the file extension used to look for mapping files under + * + * @param string $fileExtension The file extension to set + * @return void + */ + public function setFileExtension($fileExtension) + { + $this->_fileExtension = $fileExtension; + } + + /** + * Get the element of schema meta data for the class from the mapping file. + * This will lazily load the mapping file if it is not loaded yet + * + * @return array $element The element of schema meta data + */ + public function getElement($className) + { + if ($file = $this->_findMappingFile($className)) { + $result = $this->_loadMappingFile($file); + return $result[$className]; + } + return false; + } + + /** + * Finds the mapping file for the class with the given name by searching + * through the configured paths. + * + * @param $className + * @return string The (absolute) file name. + * @throws MappingException + */ + protected function _findMappingFile($className) + { + $fileName = str_replace('\\', '.', $className) . $this->_fileExtension; + + // Check whether file exists + foreach ((array) $this->_paths as $path) { + if (file_exists($path . DIRECTORY_SEPARATOR . $fileName)) { + return $path . DIRECTORY_SEPARATOR . $fileName; + } + } + + return false; + } + + /** + * Loads a mapping file with the given name and returns a map + * from class/document names to their corresponding elements. + * + * @param string $file The mapping file to load. + * @return array + */ + abstract protected function _loadMappingFile($file); +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AnnotationDriver.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AnnotationDriver.php new file mode 100644 index 00000000000..bdf2e04aad6 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AnnotationDriver.php @@ -0,0 +1,229 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping\Driver; + +use Doctrine\ODM\MongoDB\Mapping\ClassMetadata, + Doctrine\Common\Annotations\AnnotationReader; + +require __DIR__ . '/DoctrineAnnotations.php'; + +/** + * The AnnotationDriver reads the mapping metadata from docblock annotations. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class AnnotationDriver implements Driver +{ + /** + * The AnnotationReader. + * + * @var AnnotationReader + */ + private $_reader; + + /** + * The paths where to look for mapping files. + * + * @var array + */ + private $_paths = array(); + + /** + * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading + * docblock annotations. + * + * @param $reader The AnnotationReader to use. + * @param string|array $paths One or multiple paths where mapping classes can be found. + */ + public function __construct(AnnotationReader $reader, $paths = null) + { + $this->_reader = $reader; + if ($paths) { + $this->addPaths((array) $paths); + } + } + + /** + * Append lookup paths to metadata driver. + * + * @param array $paths + */ + public function addPaths(array $paths) + { + $this->_paths = array_unique(array_merge($this->_paths, $paths)); + } + + /** + * Retrieve the defined metadata lookup paths. + * + * @return array + */ + public function getPaths() + { + return $this->_paths; + } + + /** + * {@inheritdoc} + */ + public function loadMetadataForClass($className, ClassMetadata $class) + { + $reflClass = $class->getReflectionClass(); + + $classAnnotations = $this->_reader->getClassAnnotations($reflClass); + if (isset($classAnnotations['Doctrine\ODM\MongoDB\Mapping\Document'])) { + $documentAnnot = $classAnnotations['Doctrine\ODM\MongoDB\Mapping\Document']; + if ($documentAnnot->db) { + $class->setDB($documentAnnot->db); + } + if ($documentAnnot->collection) { + $class->setCollection($documentAnnot->collection); + } + if ($documentAnnot->repositoryClass) { + $class->setCustomRepositoryClass($documentAnnot->repositoryClass); + } + if ($documentAnnot->indexes) { + foreach($documentAnnot->indexes as $index) { + $class->addIndex($index->keys, $index->options); + } + } + if (isset($classAnnotations['Doctrine\ODM\MongoDB\Mapping\InheritanceType'])) { + $inheritanceTypeAnnot = $classAnnotations['Doctrine\ODM\MongoDB\Mapping\InheritanceType']; + $class->setInheritanceType(constant('Doctrine\ODM\MongoDB\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceTypeAnnot->value)); + } + if (isset($classAnnotations['Doctrine\ODM\MongoDB\Mapping\DiscriminatorField'])) { + $discrFieldAnnot = $classAnnotations['Doctrine\ODM\MongoDB\Mapping\DiscriminatorField']; + $class->setDiscriminatorField(array( + 'fieldName' => $discrFieldAnnot->fieldName, + )); + } + if (isset($classAnnotations['Doctrine\ODM\MongoDB\Mapping\DiscriminatorMap'])) { + $discrMapAnnot = $classAnnotations['Doctrine\ODM\MongoDB\Mapping\DiscriminatorMap']; + $class->setDiscriminatorMap($discrMapAnnot->value); + } + } else if (isset($classAnnotations['Doctrine\ODM\MongoDB\Mapping\MappedSuperclass'])) { + $class->isMappedSuperclass = true; + } else if (isset($classAnnotations['Doctrine\ODM\MongoDB\Mapping\EmbeddedDocument'])) { + $class->isEmbeddedDocument = true; + } + + foreach ($reflClass->getProperties() as $property) { + $mapping = array(); + $mapping['fieldName'] = $property->getName(); + + if ($alsoLoad = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ODM\MongoDB\Mapping\AlsoLoad')) { + $class->fieldMappings[$mapping['fieldName']]['alsoLoadFields'] = (array) $alsoLoad->value; + } + if ($notSaved = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ODM\MongoDB\Mapping\NotSaved')) { + $class->fieldMappings[$mapping['fieldName']]['notSaved'] = true; + } + + $types = array( + 'Id', 'Increment', 'File', 'Field', 'String', 'Boolean', 'Int', 'Float', 'Date', + 'Key', 'Bin', 'BinFunc', 'BinUUID', 'BinMD5', 'BinCustom', 'EmbedOne', + 'EmbedMany', 'ReferenceOne', 'ReferenceMany', 'Timestamp', 'Hash', 'Collection' + ); + foreach ($types as $type) { + if ($fieldAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ODM\MongoDB\Mapping\\' . $type)) { + if ($type === 'Id' && $fieldAnnot->custom) { + $fieldAnnot->type = 'custom_id'; + $class->setAllowCustomId(true); + } + $mapping = array_merge($mapping, (array) $fieldAnnot); + $class->mapField($mapping); + break; + } + } + $types = array('Embed', 'Reference'); + foreach ($types as $type) { + if ($fieldAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ODM\MongoDB\Mapping\\' . $type)) { + // This is a blatant hack to see if the defined default + // value is an array so we can make the embed/reference many + // instead of one. This won't be necessary once the ReflectionProperty + // class has a getDefaultValue() method: http://bugs.php.net/bug.php?id=41670 + $property->setAccessible(true); + $default = $property->getValue(new $class->name); + $mapping = array_merge($mapping, (array) $fieldAnnot); + if (is_array($default)) { + $mapping['type'] = 'many'; + } else { + $mapping['type'] = 'one'; + } + $class->mapField($mapping); + } + } + // Remove transient fields + if ($transientAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ODM\MongoDB\Mapping\Transient')) { + unset($class->fieldMappings[$mapping['fieldName']]); + } + } + + $methods = $reflClass->getMethods(); + foreach ($methods as $method) { + if ($method->isPublic()) { + if ($alsoLoad = $this->_reader->getMethodAnnotation($method, 'Doctrine\ODM\MongoDB\Mapping\AlsoLoad')) { + $class->fieldMappings[$mapping['fieldName']]['alsoLoadMethods'][] = array( + 'name' => (array) $alsoLoad->value, + 'method' => $method->getName() + ); + } + } + } + if (isset($classAnnotations['Doctrine\ODM\MongoDB\Mapping\HasLifecycleCallbacks'])) { + foreach ($methods as $method) { + if ($method->isPublic()) { + $annotations = $this->_reader->getMethodAnnotations($method); + + if (isset($annotations['Doctrine\ODM\MongoDB\Mapping\PrePersist'])) { + $class->addLifecycleCallback($method->getName(), \Doctrine\ODM\MongoDB\ODMEvents::prePersist); + } + + if (isset($annotations['Doctrine\ODM\MongoDB\Mapping\PostPersist'])) { + $class->addLifecycleCallback($method->getName(), \Doctrine\ODM\MongoDB\ODMEvents::postPersist); + } + + if (isset($annotations['Doctrine\ODM\MongoDB\Mapping\PreUpdate'])) { + $class->addLifecycleCallback($method->getName(), \Doctrine\ODM\MongoDB\ODMEvents::preUpdate); + } + + if (isset($annotations['Doctrine\ODM\MongoDB\Mapping\PostUpdate'])) { + $class->addLifecycleCallback($method->getName(), \Doctrine\ODM\MongoDB\ODMEvents::postUpdate); + } + + if (isset($annotations['Doctrine\ODM\MongoDB\Mapping\PreRemove'])) { + $class->addLifecycleCallback($method->getName(), \Doctrine\ODM\MongoDB\ODMEvents::preRemove); + } + + if (isset($annotations['Doctrine\ODM\MongoDB\Mapping\PostRemove'])) { + $class->addLifecycleCallback($method->getName(), \Doctrine\ODM\MongoDB\ODMEvents::postRemove); + } + + if (isset($annotations['Doctrine\ODM\MongoDB\Mapping\PostLoad'])) { + $class->addLifecycleCallback($method->getName(), \Doctrine\ODM\MongoDB\ODMEvents::postLoad); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Driver/DoctrineAnnotations.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Driver/DoctrineAnnotations.php new file mode 100644 index 00000000000..4fd94fa7855 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Driver/DoctrineAnnotations.php @@ -0,0 +1,183 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping; + +use Doctrine\Common\Annotations\Annotation; + +final class Document extends Annotation +{ + public $db; + public $collection; + public $repositoryClass; + public $indexes = array(); +} +final class EmbeddedDocument extends Annotation {} +final class MappedSuperclass extends Annotation {} + +final class Inheritance extends Annotation +{ + public $type = 'NONE'; + public $discriminatorMap = array(); + public $discriminatorField; +} +final class InheritanceType extends Annotation {} +final class DiscriminatorField extends Annotation +{ + public $name; + public $fieldName; +} +final class DiscriminatorMap extends Annotation {} + +final class Index extends Annotation +{ + public $keys = array(); + public $options = array(); +} + +final class Id extends Annotation +{ + public $id = true; + public $type = 'id'; + public $custom = false; +} +class Field extends Annotation +{ + public $type = 'string'; + public $nullable = false; +} +final class Hash extends Field +{ + public $type = 'hash'; +} +final class Boolean extends Field +{ + public $type = 'boolean'; +} +final class Int extends Field +{ + public $type = 'int'; +} +final class Float extends Field +{ + public $type = 'float'; +} +final class String extends Field +{ + public $type = 'string'; +} +final class Date extends Field +{ + public $type = 'date'; +} +final class Key extends Field +{ + public $type = 'key'; +} +final class Timestamp extends Field +{ + public $type = 'timestamp'; +} +final class Bin extends Field +{ + public $type = 'bin'; +} +final class BinFunc extends Field +{ + public $type = 'bin_func'; +} +final class BinUUID extends Field +{ + public $type = 'bin_uuid'; +} +final class BinMD5 extends Field +{ + public $type = 'bin_md5'; +} +final class BinCustom extends Field +{ + public $type = 'bin_custom'; +} +final class File extends Field +{ + public $type = 'file'; + public $file = true; +} +final class Increment extends Field +{ + public $type = 'increment'; +} +final class Collection extends Field +{ + public $type = 'collection'; +} +final class EmbedOne extends Field +{ + public $type = 'one'; + public $embedded = true; + public $targetDocument; +} +final class Embed extends Field +{ + public $embedded = true; + public $targetDocument; +} +final class EmbedMany extends Field +{ + public $type = 'many'; + public $embedded = true; + public $targetDocument; +} +final class Reference extends Field +{ + public $reference = true; + public $targetDocument; + public $cascade; +} +final class ReferenceOne extends Field +{ + public $type = 'one'; + public $reference = true; + public $targetDocument; + public $cascade; +} +final class ReferenceMany extends Field +{ + public $type = 'many'; + public $reference = true; + public $targetDocument; + public $cascade; + public $strategy = 'set'; +} +final class Transient extends Annotation {} +final class NotSaved extends Annotation {} +final class AlsoLoad extends Annotation { + public $name; +} +/* Annotations for lifecycle callbacks */ +final class HasLifecycleCallbacks extends Annotation {} +final class PrePersist extends Annotation {} +final class PostPersist extends Annotation {} +final class PreUpdate extends Annotation {} +final class PostUpdate extends Annotation {} +final class PreRemove extends Annotation {} +final class PostRemove extends Annotation {} +final class PostLoad extends Annotation {} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Driver/Driver.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Driver/Driver.php new file mode 100644 index 00000000000..173064182df --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Driver/Driver.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping\Driver; + +use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; + +/** + * Contract for metadata drivers. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +interface Driver +{ + /** + * Loads the metadata for the specified class into the provided container. + * + * @param string $className + * @param ClassMetadataInfo $metadata + */ + public function loadMetadataForClass($className, ClassMetadata $class); +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Driver/DriverChain.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Driver/DriverChain.php new file mode 100755 index 00000000000..d977d9eb14f --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Driver/DriverChain.php @@ -0,0 +1,75 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping\Driver; + +use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; + +/** + * The DriverChain allows you to add multiple other mapping drivers for + * certain namespaces + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + * @author Bulat Shakirzyanov + */ +class DriverChain implements Driver +{ + /** + * @var array + */ + private $_drivers = array(); + + /** + * Add a nested driver. + * + * @param Driver $nestedDriver + * @param string $namespace + */ + public function addDriver(Driver $nestedDriver, $namespace) + { + $this->_drivers[$namespace] = $nestedDriver; + } + + /** + * Get the array of nested drivers. + * + * @return array $drivers + */ + public function getDrivers() + { + return $this->_drivers; + } + + /** + * {@inheritdoc} + */ + public function loadMetadataForClass($className, ClassMetadata $class) + { + foreach ($this->_drivers as $namespace => $driver) { + if (strpos($className, $namespace) === 0) { + $driver->loadMetadataForClass($className, $class); + return; + } + } + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Driver/PHPDriver.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Driver/PHPDriver.php new file mode 100644 index 00000000000..04dba84611f --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Driver/PHPDriver.php @@ -0,0 +1,45 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping\Driver; + +use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; + +/** + * The PHPDriver invokes a static PHP function on the document class itself passing + * a ClassMetadata instance for you to manually populate with mapping information. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class PHPDriver implements Driver +{ + /** + * {@inheritdoc} + */ + public function loadMetadataForClass($className, ClassMetadata $class) + { + if (method_exists($className, 'loadMetadata')) { + call_user_func_array(array($className, 'loadMetadata'), array($class)); + } + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php new file mode 100644 index 00000000000..3f1a143d574 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php @@ -0,0 +1,185 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping\Driver; + +use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; + +/** + * XmlDriver is a metadata driver that enables mapping through XML files. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class XmlDriver extends AbstractFileDriver +{ + /** + * The file extension of mapping documents. + * + * @var string + */ + protected $_fileExtension = '.dcm.xml'; + + /** + * {@inheritdoc} + */ + public function loadMetadataForClass($className, ClassMetadata $class) + { + $xmlRoot = $this->getElement($className); + if ( ! $xmlRoot) { + return; + } + + if ($xmlRoot->getName() == 'document') { + if (isset($xmlRoot['repository-class'])) { + $class->setCustomRepositoryClass((string) $xmlRoot['repository-class']); + } + } elseif ($xmlRoot->getName() == 'mapped-superclass') { + $class->isMappedSuperclass = true; + } elseif ($xmlRoot->getName() == 'embedded-document') { + $class->isEmbeddedDocument = true; + } + if (isset($xmlRoot['db'])) { + $class->setDB((string) $xmlRoot['db']); + } + if (isset($xmlRoot['collection'])) { + $class->setCollection((string) $xmlRoot['collection']); + } + if (isset($xmlRoot['indexes'])) { + foreach($xmlRoot['indexes'] as $index) { + $class->addIndex((array) $index['keys'], (array) $index['options']); + } + } + if (isset($xmlRoot['customId']) && ((string) $xmlRoot['customId'] === true)) { + $class->setAllowCustomId(true); + } + if (isset($xmlRoot['inheritance-type'])) { + $inheritanceType = (string) $xmlRoot['inheritance-type']; + $class->setInheritanceType(constant('Doctrine\ODM\MongoDB\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceType)); + } + if (isset($xmlRoot->{'discriminator-field'})) { + $discrField = $xmlRoot->{'discriminator-field'}; + $class->setDiscriminatorField(array( + 'name' => (string) $discrField['name'], + 'fieldName' => (string) $discrField['fieldName'], + )); + } + if (isset($xmlRoot->{'discriminator-map'})) { + $map = array(); + foreach ($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} AS $discrMapElement) { + $map[(string) $discrMapElement['value']] = (string) $discrMapElement['class']; + } + $class->setDiscriminatorMap($map); + } + if (isset($xmlRoot->inheritance['type'])) { + $class->discriminatorMap = $xmlRoot['inheritance']; + } + if (isset($xmlRoot->field)) { + foreach ($xmlRoot->field as $field) { + $mapping = array(); + $attributes = $field->attributes(); + foreach ($attributes as $key => $value) { + $mapping[$key] = (string) $value; + $booleanAttributes = array('id', 'reference', 'embed'); + if (in_array($key, $booleanAttributes)) { + $mapping[$key] = ('true' === $mapping[$key]) ? true : false; + } + } + $class->mapField($mapping); + } + } + if (isset($xmlRoot->{'embed-one'})) { + foreach ($xmlRoot->{'embed-one'} as $embed) { + $mapping = $this->_getMappingFromEmbed($embed, 'one'); + $class->mapField($mapping); + } + } + if (isset($xmlRoot->{'embed-many'})) { + foreach ($xmlRoot->{'embed-many'} as $embed) { + $mapping = $this->_getMappingFromEmbed($embed, 'many'); + $class->mapField($mapping); + } + } + if (isset($xmlRoot->{'reference-many'})) { + foreach ($xmlRoot->{'reference-many'} as $reference) { + $mapping = $this->_getMappingFromReference($reference, 'many'); + $class->mapField($mapping); + } + } + if (isset($xmlRoot->{'reference-one'})) { + foreach ($xmlRoot->{'reference-one'} as $reference) { + $mapping = $this->_getMappingFromReference($reference, 'one'); + $class->mapField($mapping); + } + } + if (isset($xmlRoot->{'lifecycle-callbacks'})) { + foreach ($xmlRoot->{'lifecycle-callbacks'}->{'lifecycle-callback'} as $lifecycleCallback) { + $class->addLifecycleCallback((string) $lifecycleCallback['method'], constant('Doctrine\ODM\MongoDB\ODMEvents::' . (string) $lifecycleCallback['type'])); + } + } + } + + private function _getMappingFromEmbed($embed, $type) + { + $attributes = $embed->attributes(); + $mapping = array( + 'type' => $type, + 'embedded' => true, + 'targetDocument' => (string) $attributes['target-document'], + 'name' => (string) $attributes['field'], + ); + return $mapping; + } + + private function _getMappingFromReference($reference, $type) + { + $cascade = array_keys((array) $reference->cascade); + if (1 === count($cascade)) { + $cascade = current($cascade) ?: next($cascade); + } + $attributes = $reference->attributes(); + $mapping = array( + 'cascade' => $cascade, + 'type' => $type, + 'reference' => true, + 'targetDocument' => (string) $attributes['target-document'], + 'name' => (string) $attributes['field'], + 'strategy' => (isset($attributes['strategy'])) ? (string) $attributes['strategy'] : 'set', + ); + return $mapping; + } + + protected function _loadMappingFile($file) + { + $result = array(); + $xmlElement = simplexml_load_file($file); + + if (isset($xmlElement->document)) { + foreach ($xmlElement->document as $documentElement) { + $documentName = (string) $documentElement['name']; + $result[$documentName] = $documentElement; + } + } + + return $result; + } +} diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Driver/YamlDriver.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Driver/YamlDriver.php new file mode 100644 index 00000000000..2281a060408 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Driver/YamlDriver.php @@ -0,0 +1,158 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping\Driver; + +use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; + +/** + * The YamlDriver reads the mapping metadata from yaml schema files. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class YamlDriver extends AbstractFileDriver +{ + /** + * The file extension of mapping documents. + * + * @var string + */ + protected $_fileExtension = '.dcm.yml'; + + /** + * {@inheritdoc} + */ + public function loadMetadataForClass($className, ClassMetadata $class) + { + $element = $this->getElement($className); + if ( ! $element) { + return; + } + $element['type'] = isset($element['type']) ? $element['type'] : 'document'; + + if (isset($element['db'])) { + $class->setDB($element['db']); + } + if (isset($element['collection'])) { + $class->setCollection($element['collection']); + } + if ($element['type'] == 'document') { + if (isset($element['repositoryClass'])) { + $class->setCustomRepositoryClass($element['repositoryClass']); + } + } elseif ($element['type'] === 'mappedSuperclass') { + $class->isMappedSuperclass = true; + } elseif ($element['type'] === 'embeddedDocument') { + $class->isEmbeddedDocument = true; + } + if (isset($element['indexes'])) { + foreach($element['indexes'] as $index) { + $class->addIndex($index['keys'], $index['options']); + } + } + if (isset($element['inheritanceType'])) { + $class->setInheritanceType(constant('Doctrine\ODM\MongoDB\Mapping\ClassMetadata::INHERITANCE_TYPE_' . strtoupper($element['inheritanceType']))); + } + if (isset($element['customId']) && $element['customId']) { + $class->setAllowCustomId(true); + } + if (isset($element['discriminatorField'])) { + $discrField = $element['discriminatorField']; + $class->setDiscriminatorField(array( + 'name' => $discrField['name'], + 'fieldName' => $discrField['fieldName'] + )); + } + if (isset($element['discriminatorMap'])) { + $class->setDiscriminatorMap($element['discriminatorMap']); + } + if (isset($element['fields'])) { + foreach ($element['fields'] as $fieldName => $mapping) { + if ( ! isset($mapping['fieldName'])) { + $mapping['fieldName'] = $fieldName; + } + $class->mapField($mapping); + } + } + if (isset($element['embedOne'])) { + foreach ($element['embedOne'] as $fieldName => $embed) { + $mapping = $this->_getMappingFromEmbed($fieldName, $embed, 'one'); + $class->mapField($mapping); + } + } + if (isset($element['embedMany'])) { + foreach ($element['embedMany'] as $fieldName => $embed) { + $mapping = $this->_getMappingFromEmbed($fieldName, $embed, 'many'); + $class->mapField($mapping); + } + } + if (isset($element['referenceOne'])) { + foreach ($element['referenceOne'] as $fieldName => $reference) { + $mapping = $this->_getMappingFromReference($fieldName, $reference, 'one'); + $class->mapField($mapping); + } + } + if (isset($element['referenceMany'])) { + foreach ($element['referenceMany'] as $fieldName => $reference) { + $mapping = $this->_getMappingFromReference($fieldName, $reference, 'many'); + $class->mapField($mapping); + } + } + if (isset($element['lifecycleCallbacks'])) { + foreach ($element['lifecycleCallbacks'] as $type => $methods) { + foreach ($methods as $method) { + $class->addLifecycleCallback($method, constant('Doctrine\ODM\MongoDB\ODMEvents::' . $type)); + } + } + } + } + + private function _getMappingFromEmbed($fieldName, $embed, $type) + { + $mapping = array( + 'name' => $fieldName, + 'embedded' => true, + 'type' => $type, + 'targetDocument' => $embed['targetDocument'], + ); + return $mapping; + } + + private function _getMappingFromReference($fieldName, $reference, $type) + { + $mapping = array( + 'cascade' => isset($reference['cascade']) ? $reference['cascade'] : null, + 'type' => $type, + 'reference' => true, + 'targetDocument' => $reference['targetDocument'], + 'name' => $fieldName, + 'strategy' => (isset($reference['strategy'])) ? $reference['strategy'] : 'set', + ); + return $mapping; + } + + protected function _loadMappingFile($file) + { + return \Symfony\Components\Yaml\Yaml::load($file); + } +} diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/BinDataCustomType.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/BinDataCustomType.php new file mode 100644 index 00000000000..253c5a6b603 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/BinDataCustomType.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping\Types; + +/** + * The BinDataCustom type. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class BinDataCustomType extends Type +{ + public function convertToDatabaseValue($value) + { + return new \MongoBinData($value, \MongoBinData::CUSTOM); + } + + public function convertToPHPValue($value) + { + return $value->bin; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/BinDataFuncType.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/BinDataFuncType.php new file mode 100644 index 00000000000..55f686fbc5d --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/BinDataFuncType.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping\Types; + +/** + * The BinDataFunc type. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class BinDataFuncType extends Type +{ + public function convertToDatabaseValue($value) + { + return new \MongoBinData($value, \MongoBinData::FUNC); + } + + public function convertToPHPValue($value) + { + return $value->bin; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/BinDataMD5Type.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/BinDataMD5Type.php new file mode 100644 index 00000000000..5048bf31118 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/BinDataMD5Type.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping\Types; + +/** + * The BinDataMD5 type. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class BinDataMD5Type extends Type +{ + public function convertToDatabaseValue($value) + { + return new \MongoBinData($value, \MongoBinData::MD5); + } + + public function convertToPHPValue($value) + { + return $value->bin; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/BinDataType.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/BinDataType.php new file mode 100644 index 00000000000..3223a9b7674 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/BinDataType.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping\Types; + +/** + * The BinData type. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class BinDataType extends Type +{ + public function convertToDatabaseValue($value) + { + return new \MongoBinData($value, \MongoBinData::BYTE_ARRAY); + } + + public function convertToPHPValue($value) + { + return $value->bin; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/BinDataUUIDType.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/BinDataUUIDType.php new file mode 100644 index 00000000000..59ffeb63d0b --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/BinDataUUIDType.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping\Types; + +/** + * The BinDataUUID type. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class BinDataUUIDType extends Type +{ + public function convertToDatabaseValue($value) + { + return new \MongoBinData($value, \MongoBinData::UUID); + } + + public function convertToPHPValue($value) + { + return $value->bin; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/BooleanType.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/BooleanType.php new file mode 100644 index 00000000000..da519bdeb78 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/BooleanType.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping\Types; + +/** + * The Boolean type. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class BooleanType extends Type +{ + public function convertToDatabaseValue($value) + { + return (boolean) $value; + } + + public function convertToPHPValue($value) + { + return (boolean) $value; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/CollectionType.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/CollectionType.php new file mode 100755 index 00000000000..89aef6bf7ab --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/CollectionType.php @@ -0,0 +1,43 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping\Types; + +/** + * The Collection type. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + * @author Bulat Shakirzyanov + */ +class CollectionType extends Type +{ + public function convertToDatabaseValue($value) + { + return array_values((array) $value); + } + + public function convertToPHPValue($value) + { + return array_values((array) $value); + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/CustomIdType.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/CustomIdType.php new file mode 100755 index 00000000000..122b194c7f8 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/CustomIdType.php @@ -0,0 +1,43 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping\Types; + +/** + * The Id type. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + * @author Bulat Shakirzyanov + */ +class CustomIdType extends Type +{ + public function convertToDatabaseValue($value) + { + return (string) $value; + } + + public function convertToPHPValue($value) + { + return (string) $value; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/DateType.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/DateType.php new file mode 100644 index 00000000000..72639d296fa --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/DateType.php @@ -0,0 +1,50 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping\Types; + +/** + * The String type. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class DateType extends Type +{ + public function convertToDatabaseValue($value) + { + if ($value instanceof \DateTime) { + $value = $value->getTimestamp(); + } + if (is_string($value)) { + $value = strtotime($value); + } + return new \MongoDate($value); + } + + public function convertToPHPValue($value) + { + $date = new \DateTime(); + $date->setTimestamp($value->sec); + return $date; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/FileType.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/FileType.php new file mode 100644 index 00000000000..731dedd1880 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/FileType.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping\Types; + +/** + * The File type. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class FileType extends Type +{ + public function convertToDatabaseValue($value) + { + return $value; + } + + public function convertToPHPValue($value) + { + return $value; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/FloatType.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/FloatType.php new file mode 100644 index 00000000000..0f7a4e8555e --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/FloatType.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping\Types; + +/** + * The Float type. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class FloatType extends Type +{ + public function convertToDatabaseValue($value) + { + return (float) $value; + } + + public function convertToPHPValue($value) + { + return (float) $value; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/HashType.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/HashType.php new file mode 100755 index 00000000000..6588409ec23 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/HashType.php @@ -0,0 +1,43 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping\Types; + +/** + * The Array type. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + * @author Bulat Shakirzyanov + */ +class HashType extends Type +{ + public function convertToDatabaseValue($value) + { + return (array) $value; + } + + public function convertToPHPValue($value) + { + return (array) $value; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/IdType.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/IdType.php new file mode 100644 index 00000000000..946976ee528 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/IdType.php @@ -0,0 +1,45 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping\Types; + +/** + * The Id type. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class IdType extends Type +{ + public function convertToDatabaseValue($value) + { + if ( ! $value instanceof \MongoId) { + $value = new \MongoId($value); + } + return $value; + } + + public function convertToPHPValue($value) + { + return (string) $value; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/IncrementType.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/IncrementType.php new file mode 100644 index 00000000000..5da7b147f8f --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/IncrementType.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping\Types; + +/** + * The Increment type. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class IncrementType extends Type +{ + public function convertToDatabaseValue($value) + { + return (integer) $value; + } + + public function convertToPHPValue($value) + { + return (integer) $value; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/IntType.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/IntType.php new file mode 100644 index 00000000000..f4405fdb892 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/IntType.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping\Types; + +/** + * The Int type. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class IntType extends Type +{ + public function convertToDatabaseValue($value) + { + return (integer) $value; + } + + public function convertToPHPValue($value) + { + return (integer) $value; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/KeyType.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/KeyType.php new file mode 100644 index 00000000000..b2aa6414eb5 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/KeyType.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping\Types; + +/** + * The Key type. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class KeyType extends Type +{ + public function convertToDatabaseValue($value) + { + return $value ? new \MongoMaxKey : new \MongoMinKey; + } + + public function convertToPHPValue($value) + { + return $value instanceof \MongoMaxKey ? 1 : 0; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/StringType.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/StringType.php new file mode 100644 index 00000000000..fe41f7f4bf6 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/StringType.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping\Types; + +/** + * The String type. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class StringType extends Type +{ + public function convertToDatabaseValue($value) + { + return (string) $value; + } + + public function convertToPHPValue($value) + { + return (string) $value; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/TimestampType.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/TimestampType.php new file mode 100644 index 00000000000..6853472bdd8 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/TimestampType.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping\Types; + +/** + * The Timestamp type. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class TimestampType extends Type +{ + public function convertToDatabaseValue($value) + { + return new \MongoTimestamp($value); + } + + public function convertToPHPValue($value) + { + return (string) $value; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/Type.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/Type.php new file mode 100644 index 00000000000..39495afcaf3 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mapping/Types/Type.php @@ -0,0 +1,153 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Mapping\Types; + +use Doctrine\ODM\MongoDB\MongoDBException; + +/** + * The Type interface. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +abstract class Type +{ + /** + * Array of string types mapped to their type class. + */ + private static $_typesMap = array( + 'id' => 'Doctrine\ODM\MongoDB\Mapping\Types\IdType', + 'custom_id' => 'Doctrine\ODM\MongoDB\Mapping\Types\CustomIdType', + 'boolean' => 'Doctrine\ODM\MongoDB\Mapping\Types\BooleanType', + 'int' => 'Doctrine\ODM\MongoDB\Mapping\Types\IntType', + 'float' => 'Doctrine\ODM\MongoDB\Mapping\Types\FloatType', + 'string' => 'Doctrine\ODM\MongoDB\Mapping\Types\StringType', + 'date' => 'Doctrine\ODM\MongoDB\Mapping\Types\DateType', + 'key' => 'Doctrine\ODM\MongoDB\Mapping\Types\KeyType', + 'timestamp' => 'Doctrine\ODM\MongoDB\Mapping\Types\TimestampType', + 'bin' => 'Doctrine\ODM\MongoDB\Mapping\Types\BinDataType', + 'bin_func' => 'Doctrine\ODM\MongoDB\Mapping\Types\BinDataFuncType', + 'bin_uuid' => 'Doctrine\ODM\MongoDB\Mapping\Types\BinDataUUIDType', + 'bin_md5' => 'Doctrine\ODM\MongoDB\Mapping\Types\BinDataMD5Type', + 'custom' => 'Doctrine\ODM\MongoDB\Mapping\Types\BinDataCustomType', + 'file' => 'Doctrine\ODM\MongoDB\Mapping\Types\FileType', + 'hash' => 'Doctrine\ODM\MongoDB\Mapping\Types\HashType', + 'collection' => 'Doctrine\ODM\MongoDB\Mapping\Types\CollectionType', + 'increment' => 'Doctrine\ODM\MongoDB\Mapping\Types\IncrementType' + ); + + abstract public function convertToDatabaseValue($value); + abstract public function convertToPHPValue($value); + + /** + * Array of instantiated type classes. + */ + private static $_types = array(); + + /** + * Register a new type in the type map. + * + * @param string $name The name of the type. + * @param string $class The class name. + */ + public static function registerType($name, $class) + { + self::$_typesMap[$name] = $class; + } + + /** + * Get a Type instance. + * + * @param string $type The type name. + * @return Doctrine\ODM\MongoDB\Mapping\Types\Type $type + * @throws InvalidArgumentException + */ + public static function getType($type) + { + if ( ! isset(self::$_typesMap[$type])) { + throw new \InvalidArgumentException(sprintf('Invalid type specified "%s".', $type)); + } + if ( ! isset(self::$_types[$type])) { + $className = self::$_typesMap[$type]; + self::$_types[$type] = new $className; + } + return self::$_types[$type]; + } + + /** + * Adds a custom type to the type map. + * + * @static + * @param string $name Name of the type. This should correspond to what getName() returns. + * @param string $className The class name of the custom type. + * @throws MongoDBException + */ + public static function addType($name, $className) + { + if (isset(self::$_typesMap[$name])) { + throw MongoDBException::typeExists($name); + } + + self::$_typesMap[$name] = $className; + } + + /** + * Checks if exists support for a type. + * + * @static + * @param string $name Name of the type + * @return boolean TRUE if type is supported; FALSE otherwise + */ + public static function hasType($name) + { + return isset(self::$_typesMap[$name]); + } + + /** + * Overrides an already defined type to use a different implementation. + * + * @static + * @param string $name + * @param string $className + * @throws MongoDBException + */ + public static function overrideType($name, $className) + { + if ( ! isset(self::$_typesMap[$name])) { + throw MongoDBException::typeNotFound($name); + } + + self::$_typesMap[$name] = $className; + } + + /** + * Get the types array map which holds all registered types and the corresponding + * type class + * + * @return array $typesMap + */ + public static function getTypesMap() + { + return self::$_typesMap; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mongo.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mongo.php new file mode 100644 index 00000000000..8ce4412d6f2 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Mongo.php @@ -0,0 +1,86 @@ +. + */ + +namespace Doctrine\ODM\MongoDB; + +/** + * Wrapper for the PHP Mongo class. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + */ +class Mongo +{ + /** The PHP Mongo instance. */ + private $_mongo; + + /** + * Create a new Mongo wrapper instance. + * + * @param mixed $server A string server name, an existing Mongo instance or can be omitted. + * @param array $options + */ + public function __construct($server = null, array $options = array()) + { + if ($server instanceof \Mongo) { + $this->_mongo = $server; + } elseif ($server !== null) { + $this->_mongo = new \Mongo($server, $options); + } else { + $this->_mongo = new \Mongo(); + } + } + + /** + * Set the PHP Mongo instance to wrap. + * + * @param Mongo $mongo The PHP Mongo instance + */ + public function setMongo(\Mongo $mongo) + { + $this->_mongo = $mongo; + } + + /** + * Returns the PHP Mongo instance being wrapped. + * + * @return Mongo + */ + public function getMongo() + { + return $this->_mongo; + } + + /** @proxy */ + public function __get($key) + { + return $this->_mongo->$key; + } + + /** @proxy */ + public function __call($method, $arguments) + { + if (method_exists($this->_mongo, $method)) { + return call_user_func_array(array($this->_mongo, $method), $arguments); + } + throw new \BadMethodCallException(sprintf('Method %s does not exist on %s', $method, get_class($this))); + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/MongoCollection.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/MongoCollection.php new file mode 100644 index 00000000000..eec818eecc2 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/MongoCollection.php @@ -0,0 +1,343 @@ +. + */ + +namespace Doctrine\ODM\MongoDB; + +use Doctrine\ODM\MongoDB\Mapping\ClassMetadata, + Doctrine\ODM\MongoDB\Mapping\Types\Type, + Doctrine\ODM\MongoDB\Event\CollectionEventArgs, + Doctrine\ODM\MongoDB\Event\CollectionUpdateEventArgs; + +/** + * Wrapper for the PHP MongoCollection class. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + */ +class MongoCollection +{ + /** The PHP MongoCollection being wrapped. */ + private $_mongoCollection; + + /** The ClassMetadata instance for this collection. */ + private $_class; + + /** A callable for logging statements. */ + private $_loggerCallable; + + /** + * The event manager that is the central point of the event system. + * + * @var Doctrine\Common\EventManager + */ + private $_eventManager; + + /** + * Mongo command prefix + * @var string + */ + private $_cmd; + + /** + * Create a new MongoCollection instance that wraps a PHP MongoCollection instance + * for a given ClassMetadata instance. + * + * @param MongoCollection $mongoColleciton The MongoCollection instance. + * @param ClassMetadata $class The ClassMetadata instance. + * @param DocumentManager $dm The DocumentManager instance. + */ + public function __construct(\MongoCollection $mongoCollection, ClassMetadata $class, DocumentManager $dm) + { + $this->_mongoCollection = $mongoCollection; + $this->_class = $class; + $this->_loggerCallable = $dm->getConfiguration()->getLoggerCallable(); + $this->_cmd = $dm->getConfiguration()->getMongoCmd(); + $this->_eventManager = $dm->getEventManager(); + } + + /** + * Log something using the configured logger callable. + * + * @param array $log The array of data to log. + */ + public function log(array $log) + { + if ( ! $this->_loggerCallable) { + return; + } + $log['class'] = $this->_class->name; + $log['db'] = $this->_class->db; + $log['collection'] = $this->_class->collection; + call_user_func_array($this->_loggerCallable, array($log)); + } + + /** + * Returns teh ClassMetadata instance for this collection. + * + * @return Doctrine\ODM\MongoDB\MongoCollection + */ + public function getMongoCollection() + { + return $this->_mongoCollection; + } + + /** @override */ + public function batchInsert(array &$a, array $options = array()) + { + if ($this->_eventManager->hasListeners(CollectionEvents::preBatchInsert)) { + $this->_eventManager->dispatchEvent(CollectionEvents::preBatchInsert, new CollectionEventArgs($this, $a)); + } + + if ($this->_mongoCollection instanceof \MongoGridFS) { + foreach ($a as $key => $array) { + $this->saveFile($array); + $a[$key] = $array; + } + return $a; + } + if ($this->_loggerCallable) { + $this->log(array( + 'batchInsert' => true, + 'num' => count($a), + 'data' => $a + )); + } + $result = $this->_mongoCollection->batchInsert($a, $options); + + if ($this->_eventManager->hasListeners(CollectionEvents::postBatchInsert)) { + $this->_eventManager->dispatchEvent(CollectionEvents::postBatchInsert, new CollectionEventArgs($this, $result)); + } + + return $result; + } + + /** + * Save a file whether it exists or not already. Deletes previous file + * contents before trying to store new file contents. + * + * @param array $a Array to store + * @return array $a + */ + public function saveFile(array &$a) + { + if ($this->_eventManager->hasListeners(CollectionEvents::preSaveFile)) { + $this->_eventManager->dispatchEvent(CollectionEvents::preSaveFile, new CollectionEventArgs($this, $a)); + } + + $fileName = $this->_class->fieldMappings[$this->_class->file]['fieldName']; + $file = $a[$fileName]; + unset($a[$fileName]); + if ($file instanceof \MongoGridFSFile) { + $id = $a['_id']; + unset($a['_id']); + $set = array($this->_cmd . 'set' => $a); + + if ($this->_loggerCallable) { + $this->log(array( + 'updating' => true, + 'file' => true, + 'id' => $id, + 'set' => $set + )); + } + + $this->_mongoCollection->update(array('_id' => $id), $set); + } else { + if (isset($a['_id'])) { + $this->_mongoCollection->chunks->remove(array('files_id' => $a['_id'])); + } + if (file_exists($file)) { + if ($this->_loggerCallable) { + $this->log(array( + 'storing' => true, + 'file' => $file, + 'document' => $a + )); + } + + $id = $this->_mongoCollection->storeFile($file, $a); + } elseif (is_string($file)) { + if ($this->_loggerCallable) { + $this->log(array( + 'storing' => true, + 'bytes' => true, + 'document' => $a + )); + } + + $id = $this->_mongoCollection->storeBytes($file, $a); + } + + $file = $this->_mongoCollection->findOne(array('_id' => $id)); + } + + if ($this->_eventManager->hasListeners(CollectionEvents::postSaveFile)) { + $this->_eventManager->dispatchEvent(CollectionEvents::postSaveFile, new CollectionEventArgs($this, $file)); + } + + $a = $file->file; + $a[$this->_class->file] = $file; + return $a; + } + + /** @override */ + public function getDBRef(array $reference) + { + if ($this->_eventManager->hasListeners(CollectionEvents::preGetDBRef)) { + $this->_eventManager->dispatchEvent(CollectionEvents::preGetDBRef, new CollectionEventArgs($this, $reference)); + } + + if ($this->_loggerCallable) { + $this->log(array( + 'get' => true, + 'reference' => $reference, + )); + } + + if ($this->_class->isFile()) { + $ref = $this->_mongoCollection->getDBRef($reference); + $file = $this->_mongoCollection->findOne(array('_id' => $ref['_id'])); + $data = $file->file; + $data[$this->_class->file] = $file; + return $data; + } + $dbRef = $this->_mongoCollection->getDBRef($reference); + + if ($this->_eventManager->hasListeners(CollectionEvents::postGetDBRef)) { + $this->_eventManager->dispatchEvent(CollectionEvents::postGetDBRef, new CollectionEventArgs($this, $dbRef)); + } + + return $dbRef; + } + + /** @override */ + public function save(array &$a, array $options = array()) + { + if ($this->_eventManager->hasListeners(CollectionEvents::preSave)) { + $this->_eventManager->dispatchEvent(CollectionEvents::preSave, new CollectionEventArgs($this, $a)); + } + + if ($this->_loggerCallable) { + $this->log(array( + 'save' => true, + 'document' => $a, + 'options' => $options + )); + } + if ($this->_class->isFile()) { + $result = $this->saveFile($a); + } else { + $result = $this->_mongoCollection->save($a, $options); + } + + if ($this->_eventManager->hasListeners(CollectionEvents::postSave)) { + $this->_eventManager->dispatchEvent(CollectionEvents::postSave, new CollectionEventArgs($this, $result)); + } + + return $result; + } + + /** @override */ + public function update(array $criteria, array $newObj, array $options = array()) + { + if ($this->_eventManager->hasListeners(CollectionEvents::preUpdate)) { + $this->_eventManager->dispatchEvent(CollectionEvents::preUpdate, new CollectionUpdateEventArgs($this, $criteria, $newObj, $options)); + } + + if ($this->_loggerCallable) { + $this->log(array( + 'update' => true, + 'criteria' => $criteria, + 'newObj' => $newObj, + 'options' => $options + )); + } + $result = $this->_mongoCollection->update($criteria, $newObj, $options); + + if ($this->_eventManager->hasListeners(CollectionEvents::postUpdate)) { + $this->_eventManager->dispatchEvent(CollectionEvents::postUpdate, new CollectionEventArgs($this, $result)); + } + + return $result; + } + + /** @override */ + public function find(array $query = array(), array $fields = array()) + { + if ($this->_eventManager->hasListeners(CollectionEvents::preFind)) { + $this->_eventManager->dispatchEvent(CollectionEvents::preFind, new CollectionEventArgs($this, $query)); + } + + if ($this->_loggerCallable) { + $this->log(array( + 'find' => true, + 'query' => $query, + 'fields' => $fields + )); + } + $result = $this->_mongoCollection->find($query, $fields); + + if ($this->_eventManager->hasListeners(CollectionEvents::postFind)) { + $this->_eventManager->dispatchEvent(CollectionEvents::postFind, new CollectionEventArgs($this, $result)); + } + + return $result; + } + + /** @override */ + public function findOne(array $query = array(), array $fields = array()) + { + if ($this->_eventManager->hasListeners(CollectionEvents::preFindOne)) { + $this->_eventManager->dispatchEvent(CollectionEvents::preFindOne, new CollectionEventArgs($this, $query)); + } + + if ($this->_loggerCallable) { + $this->log(array( + 'findOne' => true, + 'query' => $query, + 'fields' => $fields + )); + } + + if ($this->_mongoCollection instanceof \MongoGridFS) { + $file = $this->_mongoCollection->findOne($query); + $data = $file->file; + $data[$this->_class->file] = $file; + return $data; + } + $result = $this->_mongoCollection->findOne($query, $fields); + + if ($this->_eventManager->hasListeners(CollectionEvents::postFindOne)) { + $this->_eventManager->dispatchEvent(CollectionEvents::postFindOne, new CollectionEventArgs($this, $result)); + } + + return $result; + } + + /** @proxy */ + public function __call($method, $arguments) + { + if (method_exists($this->_mongoCollection, $method)) { + return call_user_func_array(array($this->_mongoCollection, $method), $arguments); + } + throw new \BadMethodCallException(sprintf('Method %s does not exist on %s', $method, get_class($this))); + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/MongoCursor.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/MongoCursor.php new file mode 100644 index 00000000000..aadd436256f --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/MongoCursor.php @@ -0,0 +1,164 @@ +. + */ + +namespace Doctrine\ODM\MongoDB; + +use Doctrine\ODM\MongoDB\Mapping\ClassMetadata, + Doctrine\ODM\MongoDB\Hydrator; + +/** + * Wrapper for the PHP MongoCursor class. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + */ +class MongoCursor implements \Iterator +{ + /** The DocumentManager instance. */ + private $_dm; + + /** The UnitOfWork instance. */ + private $_uow; + + /** The ClassMetadata instance. */ + private $_class; + + /** The PHP MongoCursor being wrapped */ + private $_mongoCursor; + + /** Whether or not to try and hydrate the returned data */ + private $_hydrate = true; + + /** + * Create a new MongoCursor which wraps around a given PHP MongoCursor. + * + * @param DocumentManager $dm + * @param Hydrator $hydrator + * @param ClassMetadata $class + * @param MongoCursor $mongoCursor + */ + public function __construct(DocumentManager $dm, Hydrator $hydrator, ClassMetadata $class, \MongoCursor $mongoCursor) + { + $this->_dm = $dm; + $this->_uow = $this->_dm->getUnitOfWork(); + $this->_hydrator = $hydrator; + $this->_class = $class; + $this->_mongoCursor = $mongoCursor; + } + + /** + * Returns the MongoCursor instance being wrapped. + * + * @return MongoCursor $mongoCursor The MongoCursor instance being wrapped. + */ + public function getMongoCursor() + { + return $this->_mongoCursor; + } + + /** + * Whether or not to try and hydrate the returned data + * + * @param boolean $bool + */ + public function hydrate($bool) + { + $this->_hydrate = $bool; + return $this; + } + + /** @override */ + public function current() + { + if ($this->_mongoCursor instanceof \MongoGridFSCursor) { + $file = $this->_mongoCursor->current(); + $current = $file->file; + $current[$this->_class->file] = $file; + } else { + $current = $this->_mongoCursor->current(); + } + if ($this->_hydrate) { + return $this->_uow->getOrCreateDocument($this->_class->name, $current); + } else { + return $current; + } + } + + /** @proxy */ + public function next() + { + return $this->_mongoCursor->next(); + } + + /** @proxy */ + public function key() + { + return $this->_mongoCursor->key(); + } + + /** @proxy */ + public function valid() + { + return $this->_mongoCursor->valid(); + } + + /** @proxy */ + public function rewind() + { + return $this->_mongoCursor->rewind(); + } + + /** + * Returns an array by converting the iterator to an array. + * + * @return array $results + */ + public function getResults() + { + return iterator_to_array($this); + } + + /** + * Get the first single result from the cursor. + * + * @return object $document The single document. + */ + public function getSingleResult() + { + if ($results = $this->getResults()) { + return array_shift($results); + } + return null; + } + + /** @proxy */ + public function __call($method, $arguments) + { + if (method_exists($this->_mongoCursor, $method)) { + $return = call_user_func_array(array($this->_mongoCursor, $method), $arguments); + if ($return === $this->_mongoCursor) { + return $this; + } + return $return; + } + throw new \BadMethodCallException(sprintf('Method %s does not exist on %s', $method, get_class($this))); + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/MongoDB.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/MongoDB.php new file mode 100644 index 00000000000..3c117c47cdb --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/MongoDB.php @@ -0,0 +1,73 @@ +. + */ + +namespace Doctrine\ODM\MongoDB; + +/** + * Wrapper for the PHP MongoDB class. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + */ +class MongoDB +{ + /** The PHP MongoDB instance being wrapped */ + private $_mongoDB; + + /** + * Create a new MongoDB instance which wraps a PHP MongoDB instance. + * + * @param MongoDB $mongoDB The MongoDB instance to wrap. + */ + public function __construct(\MongoDB $mongoDB) + { + $this->_mongoDB = $mongoDB; + } + + public function getName() + { + return (string) $this->_mongoDB; + } + + /** + * Get the MongoDB instance being wrapped. + * + * @return MongoDB $mongoDB + */ + public function getMongoDB() + { + return $this->_mongoDB; + } + + public function selectCollection($collection) + { + return $this->_mongoDB->selectCollection($collection); + } + + /** @proxy */ + public function __call($method, $arguments) + { + if (method_exists($this->_mongoDB, $method)) { + return call_user_func_array(array($this->_mongoDB, $method), $arguments); + } + throw new \BadMethodCallException(sprintf('Method %s does not exist on %s', $method, get_class($this))); + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/MongoDBException.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/MongoDBException.php new file mode 100644 index 00000000000..806ece7dddb --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/MongoDBException.php @@ -0,0 +1,96 @@ +. + */ + +namespace Doctrine\ODM\MongoDB; + +/** + * Class for all exceptions related to the Doctrine MongoDB ODM + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + */ +class MongoDBException extends \Exception +{ + public static function invalidFindByCall($documentName, $fieldName, $method) + { + return new self(sprintf('Invalid find by call %s::$fieldName (%s)', $documentName, $fieldName, $method)); + } + + public static function removedDocumentInCollectionDetected($document, $mapping) + { + return new self(sprintf('Removed document in collection detected "%s"', get_class($document), $mapping['fieldName'])); + } + + public static function detachedDocumentCannotBeRemoved() + { + return new self('Detached document cannot be removed'); + } + + public static function invalidDocumentState($state) + { + return new self(sprintf('Invalid document state "%s"', $state)); + } + + public static function mappingFileNotFound($className, $fileName) + { + return new self(sprintf('Could not find mapping file "%s" for class "%s".', $fileName, $className)); + } + + public static function documentNotMappedToDB($className) + { + return new self(sprintf('The "%s" document is not mapped to a MongoDB database.', $className)); + } + + public static function documentNotMappedToCollection($className) + { + return new self(sprintf('The "%s" document is not mapped to a MongoDB database collection.', $className)); + } + + public static function documentNotFound($className, $identifier) + { + return new self(sprintf('The "%s" document with identifier "%s" could not be found.', $className, $identifier)); + } + + public static function documentManagerClosed() + { + return new self('The DocumentManager is closed.'); + } + + public static function typeExists($name) + { + return new self('Type '.$name.' already exists.'); + } + + public static function unknownFieldType($name) + { + return new self('Unknown field type '.$name.' requested.'); + } + + public static function typeNotFound($name) + { + return new self('Type to be overwritten '.$name.' does not exist.'); + } + + public static function unknownDocumentNamespace($documentNamespaceAlias) + { + return new self("Unknown Document namespace alias '$documentNamespaceAlias'."); + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/ODMEvents.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/ODMEvents.php new file mode 100644 index 00000000000..0fd9a28cffb --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/ODMEvents.php @@ -0,0 +1,140 @@ +. + */ + +namespace Doctrine\ODM\MongoDB; + +/** + * Container for all ODM events. + * + * This class cannot be instantiated. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +final class ODMEvents +{ + private function __construct() {} + + /** + * The preRemove event occurs for a given document before the respective + * DocumentManager remove operation for that document is executed. + * + * This is an document lifecycle event. + * + * @var string + */ + const preRemove = 'preRemove'; + + /** + * The postRemove event occurs for an document after the document has + * been deleted. It will be invoked after the database delete operations. + * + * This is an document lifecycle event. + * + * @var string + */ + const postRemove = 'postRemove'; + + /** + * The prePersist event occurs for a given document before the respective + * DocumentManager persist operation for that document is executed. + * + * This is an document lifecycle event. + * + * @var string + */ + const prePersist = 'prePersist'; + + /** + * The postPersist event occurs for an document after the document has + * been made persistent. It will be invoked after the database insert operations. + * Generated primary key values are available in the postPersist event. + * + * This is an document lifecycle event. + * + * @var string + */ + const postPersist = 'postPersist'; + + /** + * The preUpdate event occurs before the database update operations to + * document data. + * + * This is an document lifecycle event. + * + * @var string + */ + const preUpdate = 'preUpdate'; + + /** + * The postUpdate event occurs after the database update operations to + * document data. + * + * This is an document lifecycle event. + * + * @var string + */ + const postUpdate = 'postUpdate'; + + /** + * The postLoad event occurs for an document after the document has been loaded + * into the current DocumentManager from the database or after the refresh operation + * has been applied to it. + * + * Note that the postLoad event occurs for an document before any associations have been + * initialized. Therefore it is not safe to access associations in a postLoad callback + * or event handler. + * + * This is an document lifecycle event. + * + * @var string + */ + const postLoad = 'postLoad'; + + /** + * The loadClassMetadata event occurs after the mapping metadata for a class + * has been loaded from a mapping source (annotations/xml/yaml). + * + * @var string + */ + const loadClassMetadata = 'loadClassMetadata'; + + /** + * The onFlush event occurs when the DocumentManager#flush() operation is invoked, + * after any changes to managed documents have been determined but before any + * actual database operations are executed. The event is only raised if there is + * actually something to do for the underlying UnitOfWork. If nothing needs to be done, + * the onFlush event is not raised. + * + * @var string + */ + const onFlush = 'onFlush'; + + /** + * The onUpdatePrepared event occurs when the BasicDocumentPersister prepared + * update array for document, including atomic operators, right before that + * array is executed in mongo. + */ + const onUpdatePrepared = 'onUpdatePrepared'; +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/PersistentCollection.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/PersistentCollection.php new file mode 100644 index 00000000000..d79058b6b61 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/PersistentCollection.php @@ -0,0 +1,540 @@ +. + */ + +namespace Doctrine\ODM\MongoDB; + +use Doctrine\Common\Collections\Collection, + Doctrine\ODM\MongoDB\Mapping\ClassMetadata, + Doctrine\ODM\MongoDB\Proxy\Proxy, + Closure; + +/** + * A PersistentCollection represents a collection of elements that have persistent state. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +final class PersistentCollection implements Collection +{ + /** + * A snapshot of the collection at the moment it was fetched from the database. + * This is used to create a diff of the collection at commit time. + * + * @var array + */ + private $_snapshot = array(); + + protected $_owner; + + protected $_mapping; + + /** + * The DocumentManager that manages the persistence of the collection. + * + * @var Doctrine\ODM\MongoDB\DocumentManager + */ + private $_dm; + + /** + * The class descriptor of the collection's document type. + */ + private $_typeClass; + + /** + * Whether the collection is dirty and needs to be synchronized with the database + * when the UnitOfWork that manages its persistent state commits. + * + * @var boolean + */ + private $_isDirty = false; + + /** + * Whether the collection has already been initialized. + * + * @var boolean + */ + private $_initialized = true; + + /** + * The wrapped Collection instance. + * + * @var Collection + */ + private $_coll; + + /** + * Mongo command prefix + * @var string + */ + private $_cmd; + + public function __construct(DocumentManager $dm, ClassMetadata $class, Collection $coll) + { + $this->_coll = $coll; + $this->_dm = $dm; + $this->_typeClass = $class; + $this->_cmd = $dm->getConfiguration()->getMongoCmd(); + } + + /** + * Initializes the collection by loading its contents from the database + * if the collection is not yet initialized. + */ + private function _initialize() + { + if ( ! $this->_initialized) { + $collection = $this->_dm->getDocumentCollection($this->_typeClass->name); + + $ids = array(); + foreach ($this->_coll as $document) { + $ids[] = $this->_typeClass->getIdentifierObject($document); + } + + $data = $collection->find(array('_id' => array($this->_cmd . 'in' => $ids))); + $hints = array(Query::HINT_REFRESH => Query::HINT_REFRESH); + foreach ($data as $id => $document) { + $document = $this->_dm->getUnitOfWork()->getOrCreateDocument($this->_typeClass->name, $document, $hints); + if ($document instanceof Proxy) { + $document->__isInitialized__ = true; + unset($document->__dm); + unset($document->__identifier); + } + } + + $this->_initialized = true; + } + } + + /** + * Marks this collection as changed/dirty. + */ + private function _changed() + { + if ( ! $this->_isDirty) { + $this->_isDirty = true; + } + } + + /** + * Gets a boolean flag indicating whether this colleciton is dirty which means + * its state needs to be synchronized with the database. + * + * @return boolean TRUE if the collection is dirty, FALSE otherwise. + */ + public function isDirty() + { + return $this->_isDirty; + } + + /** + * Sets a boolean flag, indicating whether this collection is dirty. + * + * @param boolean $dirty Whether the collection should be marked dirty or not. + */ + public function setDirty($dirty) + { + $this->_isDirty = $dirty; + } + + /** + * INTERNAL: + * Sets the collection's owning entity together with the AssociationMapping that + * describes the association between the owner and the elements of the collection. + * + * @param object $document + * @param AssociationMapping $mapping + */ + public function setOwner($document, array $mapping) + { + $this->_owner = $document; + $this->_mapping = $mapping; + } + + /** + * INTERNAL: + * Tells this collection to take a snapshot of its current state. + */ + public function takeSnapshot() + { + $this->_snapshot = $this->_coll->toArray(); + $this->_isDirty = false; + } + + /** + * INTERNAL: + * Returns the last snapshot of the elements in the collection. + * + * @return array The last snapshot of the elements. + */ + public function getSnapshot() + { + return $this->_snapshot; + } + + /** + * INTERNAL: + * getDeleteDiff + * + * @return array + */ + public function getDeleteDiff() + { + return array_udiff_assoc($this->_snapshot, $this->_coll->toArray(), + function($a, $b) {return $a === $b ? 0 : 1;}); + } + + /** + * INTERNAL: + * getInsertDiff + * + * @return array + */ + public function getInsertDiff() + { + return array_udiff_assoc($this->_coll->toArray(), $this->_snapshot, + function($a, $b) {return $a === $b ? 0 : 1;}); + } + + /** + * INTERNAL: + * Gets the collection owner. + * + * @return object + */ + public function getOwner() + { + return $this->_owner; + } + + public function getMapping() + { + return $this->_mapping; + } + + public function getTypeClass() + { + return $this->_typeClass; + } + + /** + * Sets the initialized flag of the collection, forcing it into that state. + * + * @param boolean $bool + */ + public function setInitialized($bool) + { + $this->_initialized = $bool; + } + + /** + * Checks whether this collection has been initialized. + * + * @return boolean + */ + public function isInitialized() + { + return $this->_initialized; + } + + /** {@inheritdoc} */ + public function first() + { + $this->_initialize(); + return $this->_coll->first(); + } + + /** {@inheritdoc} */ + public function last() + { + $this->_initialize(); + return $this->_coll->last(); + } + + /** + * {@inheritdoc} + */ + public function remove($key) + { + $this->_initialize(); + $removed = $this->_coll->remove($key); + return $removed; + } + + /** + * {@inheritdoc} + */ + public function removeElement($element) + { + $this->_initialize(); + $result = $this->_coll->removeElement($element); + $this->_changed(); + return $result; + } + + /** + * {@inheritdoc} + */ + public function containsKey($key) + { + $this->_initialize(); + return $this->_coll->containsKey($key); + } + + /** + * {@inheritdoc} + */ + public function contains($element) + { + $this->_initialize(); + return $this->_coll->contains($element); + } + + /** + * {@inheritdoc} + */ + public function exists(Closure $p) + { + $this->_initialize(); + return $this->_coll->exists($p); + } + + /** + * {@inheritdoc} + */ + public function indexOf($element) + { + $this->_initialize(); + return $this->_coll->indexOf($element); + } + + /** + * {@inheritdoc} + */ + public function get($key) + { + $this->_initialize(); + return $this->_coll->get($key); + } + + /** + * {@inheritdoc} + */ + public function getKeys() + { + $this->_initialize(); + return $this->_coll->getKeys(); + } + + /** + * {@inheritdoc} + */ + public function getValues() + { + $this->_initialize(); + return $this->_coll->getValues(); + } + + /** + * {@inheritdoc} + */ + public function count() + { + $this->_initialize(); + return $this->_coll->count(); + } + + /** + * {@inheritdoc} + */ + public function set($key, $value) + { + $this->_initialize(); + $this->_coll->set($key, $value); + $this->_changed(); + } + + /** + * {@inheritdoc} + */ + public function add($value) + { + $this->_coll->add($value); + $this->_changed(); + return true; + } + + /** + * {@inheritdoc} + */ + public function isEmpty() + { + $this->_initialize(); + return $this->_coll->isEmpty(); + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + $this->_initialize(); + return $this->_coll->getIterator(); + } + + /** + * {@inheritdoc} + */ + public function map(Closure $func) + { + $this->_initialize(); + return $this->_coll->map($func); + } + + /** + * {@inheritdoc} + */ + public function filter(Closure $p) + { + $this->_initialize(); + return $this->_coll->filter($p); + } + + /** + * {@inheritdoc} + */ + public function forAll(Closure $p) + { + $this->_initialize(); + return $this->_coll->forAll($p); + } + + /** + * {@inheritdoc} + */ + public function partition(Closure $p) + { + $this->_initialize(); + return $this->_coll->partition($p); + } + + /** + * {@inheritdoc} + */ + public function toArray() + { + $this->_initialize(); + return $this->_coll->toArray(); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $this->_initialize(); + $result = $this->_coll->clear(); + if ($this->_mapping->isOwningSide) { + $this->_changed(); + $this->_dm->getUnitOfWork()->scheduleCollectionDeletion($this); + } + + return $result; + } + + /** + * Called by PHP when this collection is serialized. Ensures that only the + * elements are properly serialized. + * + * @internal Tried to implement Serializable first but that did not work well + * with circular references. This solution seems simpler and works well. + */ + public function __sleep() + { + return array('_coll'); + } + + /* ArrayAccess implementation */ + + /** + * @see containsKey() + */ + public function offsetExists($offset) + { + return $this->containsKey($offset); + } + + /** + * @see get() + */ + public function offsetGet($offset) + { + return $this->get($offset); + } + + /** + * @see add() + * @see set() + */ + public function offsetSet($offset, $value) + { + if ( ! isset($offset)) { + return $this->add($value); + } + return $this->set($offset, $value); + } + + /** + * @see remove() + */ + public function offsetUnset($offset) + { + return $this->remove($offset); + } + + public function key() + { + return $this->_coll->key(); + } + + /** + * Gets the element of the collection at the current iterator position. + */ + public function current() + { + return $this->_coll->current(); + } + + /** + * Moves the internal iterator position to the next element. + */ + public function next() + { + return $this->_coll->next(); + } + + /** + * Retrieves the wrapped Collection instance. + */ + public function unwrap() + { + return $this->_coll; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Persisters/BasicDocumentPersister.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Persisters/BasicDocumentPersister.php new file mode 100755 index 00000000000..d06f0d2d848 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Persisters/BasicDocumentPersister.php @@ -0,0 +1,599 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Persisters; + +use Doctrine\ODM\MongoDB\DocumentManager, + Doctrine\ODM\MongoDB\UnitOfWork, + Doctrine\ODM\MongoDB\Mapping\ClassMetadata, + Doctrine\ODM\MongoDB\MongoCursor, + Doctrine\ODM\MongoDB\Mapping\Types\Type, + Doctrine\Common\Collections\Collection, + Doctrine\ODM\MongoDB\ODMEvents, + Doctrine\ODM\MongoDB\Event\OnUpdatePreparedArgs; + +/** + * The BasicDocumentPersister is responsible for actual persisting the calculated + * changesets performed by the UnitOfWork. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @since 1.0 + * @author Jonathan H. Wage + * @author Bulat Shakirzyanov + */ +class BasicDocumentPersister +{ + /** + * The DocumentManager instance. + * + * @var Doctrine\ODM\MongoDB\DocumentManager + */ + private $_dm; + + /** + * The UnitOfWork instance. + * + * @var Doctrine\ODM\MongoDB\UnitOfWork + */ + private $_uow; + + /** + * The ClassMetadata instance for the document type being persisted. + * + * @var Doctrine\ODM\MongoDB\Mapping\ClassMetadata + */ + private $_class; + + /** + * The MongoCollection instance for this document. + * + * @var Doctrine\ODM\MongoDB\MongoCollection + */ + private $_collection; + + /** + * The string document name being persisted. + * + * @var string + */ + private $_documentName; + + /** + * Array of quered inserts for the persister to insert. + * + * @var array + */ + private $_queuedInserts = array(); + + /** + * Documents to be updated, used in executeReferenceUpdates() method + * @var array + */ + private $_documentsToUpdate = array(); + + /** + * Fields to update, used in executeReferenceUpdates() method + * @var array + */ + private $_fieldsToUpdate = array(); + + /** + * Mongo command prefix + * @var string + */ + private $_cmd; + + /** + * Initializes a new BasicDocumentPersister instance. + * + * @param Doctrine\ODM\MongoDB\DocumentManager $dm + * @param Doctrine\ODM\MongoDB\Mapping\ClassMetadata $class + */ + public function __construct(DocumentManager $dm, ClassMetadata $class) + { + $this->_dm = $dm; + $this->_uow = $dm->getUnitOfWork(); + $this->_class = $class; + $this->_documentName = $class->getName(); + $this->_collection = $dm->getDocumentCollection($class->name); + $this->_cmd = $this->_dm->getConfiguration()->getMongoCmd(); + } + + /** + * Adds a document to the queued insertions. + * The document remains queued until {@link executeInserts} is invoked. + * + * @param object $document The document to queue for insertion. + */ + public function addInsert($document) + { + $this->_queuedInserts[spl_object_hash($document)] = $document; + } + + /** + * Executes all queued document insertions and returns any generated post-insert + * identifiers that were created as a result of the insertions. + * + * If no inserts are queued, invoking this method is a NOOP. + * + * @return array An array of any generated post-insert IDs. This will be an empty array + * if the document class does not use the IDENTITY generation strategy. + */ + public function executeInserts() + { + if ( ! $this->_queuedInserts) { + return; + } + + $postInsertIds = array(); + $inserts = array(); + + foreach ($this->_queuedInserts as $oid => $document) { + $data = $this->prepareInsertData($document); + if ( ! $data) { + continue; + } + $inserts[$oid] = $data; + } + if (empty($inserts)) { + return; + } + $this->_collection->batchInsert($inserts); + + foreach ($inserts as $oid => $data) { + $document = $this->_queuedInserts[$oid]; + $postInsertIds[] = array($data['_id'], $document); + if ($this->_class->isFile()) { + $this->_dm->getHydrator()->hydrate($this->_class, $document, $data); + } + } + $this->_queuedInserts = array(); + + return $postInsertIds; + } + + /** + * Executes reference updates in case document had references to new documents, + * without identifier value + */ + public function executeReferenceUpdates() + { + foreach ($this->_documentsToUpdate as $oid => $document) + { + $update = array(); + foreach ($this->_fieldsToUpdate[$oid] as $fieldName => $fieldData) + { + list ($mapping, $value) = $fieldData; + $update[$fieldName] = $this->_prepareValue($mapping, $value); + } + $classMetadata = $this->_dm->getClassMetadata(get_class($document)); + $id = $this->_uow->getDocumentIdentifier($document); + $id = $classMetadata->getDatabaseIdentifierValue($id); + $this->_collection->update(array( + '_id' => $id + ), array( + $this->_cmd . 'set' => $update + )); + unset($this->_documentsToUpdate[$oid]); + unset($this->_fieldsToUpdate[$oid]); + } + } + + /** + * Updates persisted document, using atomic operators + * + * @param mixed $document + */ + public function update($document) + { + $id = $this->_uow->getDocumentIdentifier($document); + + $update = $this->prepareUpdateData($document); + if ( ! empty($update)) { + if ($this->_dm->getEventManager()->hasListeners(ODMEvents::onUpdatePrepared)) { + $this->_dm->getEventManager()->dispatchEvent( + ODMEvents::onUpdatePrepared, new OnUpdatePreparedArgs($this->_dm, $document, $update) + ); + } + /** + * temporary fix for @link http://jira.mongodb.org/browse/SERVER-1050 + * atomic modifiers $pushAll and $pullAll, $push, $pop and $pull + * are not allowed on the same field in one update + */ + $id = $this->_class->getDatabaseIdentifierValue($id); + if (isset($update[$this->_cmd . 'pushAll']) && isset($update[$this->_cmd . 'pullAll'])) { + $fields = array_intersect( + array_keys($update[$this->_cmd . 'pushAll']), + array_keys($update[$this->_cmd . 'pullAll']) + ); + if ( ! empty($fields)) { + $tempUpdate = array(); + foreach ($fields as $field) { + $tempUpdate[$field] = $update[$this->_cmd . 'pullAll'][$field]; + unset($update[$this->_cmd . 'pullAll'][$field]); + } + if (empty($update[$this->_cmd . 'pullAll'])) { + unset($update[$this->_cmd . 'pullAll']); + } + $tempUpdate = array( + $this->_cmd . 'pullAll' => $tempUpdate + ); + $this->_collection->update(array('_id' => $id), $tempUpdate); + } + } + $this->_collection->update(array('_id' => $id), $update); + } + } + + /** + * Removes document from mongo + * + * @param mixed $document + */ + public function delete($document) + { + $id = $this->_uow->getDocumentIdentifier($document); + + $this->_collection->remove(array( + '_id' => $this->_class->getDatabaseIdentifierValue($id) + )); + } + + /** + * Prepares insert data for document + * + * @param mixed $document + * @return array + */ + public function prepareInsertData($document) + { + $oid = spl_object_hash($document); + $changeset = $this->_uow->getDocumentChangeSet($document); + $result = array(); + foreach ($this->_class->fieldMappings as $mapping) { + if (isset($mapping['notSaved']) && $mapping['notSaved'] === true) { + continue; + } + $new = isset($changeset[$mapping['fieldName']][1]) ? $changeset[$mapping['fieldName']][1] : null; + if ($new === null && $mapping['nullable'] === false) { + continue; + } + $changeset[$mapping['fieldName']] = array(); + if ($this->_class->isIdentifier($mapping['fieldName'])) { + $result['_id'] = $this->_prepareValue($mapping, $new); + continue; + } + $result[$mapping['fieldName']] = $this->_prepareValue($mapping, $new); + if (isset($mapping['reference'])) { + $scheduleForUpdate = false; + if ($mapping['type'] === 'one') { + if (null === $result[$mapping['fieldName']][$this->_cmd . 'id']) { + $scheduleForUpdate = true; + } + } elseif ($mapping['type'] === 'many') { + foreach ($result[$mapping['fieldName']] as $ref) { + if (null === $ref[$this->_cmd . 'id']) { + $scheduleForUpdate = true; + break; + } + } + } + if ($scheduleForUpdate) { + unset($result[$mapping['fieldName']]); + $id = spl_object_hash($document); + $this->_documentsToUpdate[$id] = $document; + $this->_fieldsToUpdate[$id][$mapping['fieldName']] = array($mapping, $new); + } + } + } + return $result; + } + + /** + * Prepares update array for document, using atomic operators + * + * @param mixed $document + * @return array + */ + public function prepareUpdateData($document) + { + $oid = spl_object_hash($document); + $changeset = $this->_uow->getDocumentChangeSet($document); + $result = array(); + foreach ($this->_class->fieldMappings as $mapping) { + if (isset($mapping['notSaved']) && $mapping['notSaved'] === true) { + continue; + } + $old = isset($changeset[$mapping['fieldName']][0]) ? $changeset[$mapping['fieldName']][0] : null; + $new = isset($changeset[$mapping['fieldName']][1]) ? $changeset[$mapping['fieldName']][1] : null; + if ($this->_class->isIdentifier($mapping['fieldName'])) { + continue; + } + $new = $this->_prepareValue($mapping, $new); + $old = $this->_prepareValue($mapping, $old); + if (($mapping['type'] === 'many') || $mapping['type'] === 'collection') { + $this->_addArrayUpdateAtomicOperator($mapping, (array) $new, (array) $old, $result); + } else { + $this->_addFieldUpdateAtomicOperator($mapping, $new, $old, $result); + } + } + return $result; + } + + /** + * + * @param array $mapping + * @param mixed $value + */ + private function _prepareValue(array $mapping, $value) { + if ( ! isset($value)) { + return null; + } + if ($mapping['type'] === 'many') { + $values = $value; + $value = array(); + foreach ($values as $rawValue) { + $value[] = $this->_prepareValue(array_merge($mapping, array( + 'type' => 'one' + )), $rawValue); + } + unset($values, $rawValue); + } elseif ((isset($mapping['reference'])) || isset($mapping['embedded'])) { + $targetClass = $this->_dm->getClassMetadata($mapping['targetDocument']); + if (isset($mapping['embedded'])) { + $value = $this->_prepareDocEmbeded($targetClass, $value); + } else if (isset($mapping['reference'])) { + $value = $this->_prepareDocReference($targetClass, $value); + } + } else { + $value = Type::getType($mapping['type'])->convertToDatabaseValue($this->_getScalar($value)); + } + return $value; + } + + /** + * Gets the ClassMetadata instance of the document class this persister is used for. + * + * @return Doctrine\ODM\MongoDB\Mapping\ClassMetadata + */ + public function getClassMetadata() + { + return $this->_class; + } + + /** + * Refreshes a managed document. + * + * @param object $document The document to refresh. + */ + public function refresh($document) + { + $id = $this->_uow->getDocumentIdentifier($document); + if ($this->_dm->loadByID($this->_class->name, $id) === null) { + throw new \InvalidArgumentException(sprintf('Could not loadByID because ' . $this->_class->name . ' '.$id . ' does not exist anymore.')); + } + } + + /** + * Loads an document by a list of field criteria. + * + * @param array $query The criteria by which to load the document. + * @param object $document The document to load the data into. If not specified, + * a new document is created. + * @param $assoc The association that connects the document to load to another document, if any. + * @param array $hints Hints for document creation. + * @return object The loaded and managed document instance or NULL if the document can not be found. + * @todo Check identity map? loadById method? Try to guess whether $criteria is the id? + */ + public function load(array $query = array(), array $select = array()) + { + $result = $this->_collection->findOne($query, $select); + if ($result !== null) { + return $this->_uow->getOrCreateDocument($this->_documentName, $result); + } + return null; + } + + /** + * Lood document by its identifier. + * + * @param string $id + * @return object|null + */ + public function loadById($id) + { + $result = $this->_collection->findOne(array( + '_id' => $this->_class->getDatabaseIdentifierValue($id) + )); + if ($result !== null) { + return $this->_uow->getOrCreateDocument($this->_documentName, $result); + } + return null; + } + + /** + * Loads a list of documents by a list of field criteria. + * + * @param array $criteria + * @return array + */ + public function loadAll(array $query = array(), array $select = array()) + { + $cursor = $this->_collection->find($query, $select); + return new MongoCursor($this->_dm, $this->_dm->getHydrator(), $this->_class, $cursor); + } + + /** + * Add the atomic operator to update or remove a field from a document + * based on whether or not the value has changed. + * + * @param string $fieldName + * @param string $new + * @param string $old + * @param string $result + */ + private function _addFieldUpdateAtomicOperator(array $mapping, $new, $old, array &$result) + { + if ($this->_equals($old, $new)) { + return; + } + + if ($mapping['type'] === 'increment') { + if ($new >= $old) { + $result[$this->_cmd . 'inc'][$mapping['fieldName']] = $new - $old; + } else { + $result[$this->_cmd . 'inc'][$mapping['fieldName']] = ($old - $new) * -1; + } + } else { + if (isset($new) || $mapping['nullable'] === true) { + $result[$this->_cmd . 'set'][$mapping['fieldName']] = $new; + } else { + $result[$this->_cmd . 'unset'][$mapping['fieldName']] = true; + } + } + } + + /** + * Add the atomic operator to add new values to an array and to remove values + * from an array. + * + * @param string $fieldName + * @param array $new + * @param array $old + * @param string $result + */ + private function _addArrayUpdateAtomicOperator(array $mapping, array $new, array $old, array &$result) + { + foreach ($old as $val) { + if ( ! in_array($val, $new)) { + $result[$this->_cmd . 'pullAll'][$mapping['fieldName']][] = $val; + } + } + foreach ($new as $val) { + if ( ! in_array($val, $old)) { + $result[$this->_cmd . 'pushAll'][$mapping['fieldName']][] = $val; + } + } + } + + /** + * Performs value comparison + * @param mixed $old + * @param mixed $new + */ + private function _equals($old, $new) + { + $old = is_scalar($old) ? $old : $this->_getScalar($old); + $new = is_scalar($new) ? $new : $this->_getScalar($new); + return $new === $old; + } + + /** + * Converts value for comparison + * @param mixed $val + * @todo this conversion might not even be necessary, needs re-thinking + */ + private function _getScalar($val) + { + if ($val instanceof \MongoDate) { + return $val->sec; + } else if ($val instanceof \DateTime) { + return $val->getTimestamp(); + } else if ($val instanceof \MongoBinData) { + return Type::getType('bin')->convertToPHPValue($val); + } else if (($val instanceof \MongoId) || ($val instanceof \MongoTimestamp)) { + return (string) $val; + } else if (($val instanceof \MongoMaxKey) || ($val instanceof \MongoMinKey)) { + return Type::getType('key')->convertToPHPValue($val); + } else { + return $val; + } + } + + /** + * Returns the reference representation to be stored in mongodb or null if not applicable. + * + * @param ClassMetadata $class + * @param Document $doc + * @return array|null + */ + private function _prepareDocReference(ClassMetadata $class, $doc) + { + if ( ! is_object($doc)) { + return $doc; + } + $id = $this->_uow->getDocumentIdentifier($doc); + if (null !== $id) { + $id = $class->getPHPIdentifierValue($id); + } + $ref = array( + $this->_cmd . 'ref' => $class->getCollection(), + $this->_cmd . 'id' => $id, + $this->_cmd . 'db' => $class->getDB() + ); + return $ref; + } + + /** + * Prepares array of values to be stored in mongo to represent embedded object. + * + * @param ClassMetadata $class + * @param Document $doc + * @return array + */ + private function _prepareDocEmbeded(ClassMetadata $class, $doc) + { + if ( ! is_object($doc)) { + return $doc; + } + $changeset = array(); + foreach ($class->fieldMappings as $mapping) { + $rawValue = $class->getFieldValue($doc, $mapping['fieldName']); + if ( ! isset($rawValue)) { + continue; + } + if (isset($mapping['embedded']) || isset($mapping['reference'])) { + $classMetadata = $this->_dm->getClassMetadata($mapping['targetDocument']); + if (isset($mapping['embedded'])) { + if ($mapping['type'] == 'many') { + $value = array(); + foreach ($rawValue as $doc) { + $value[] = $this->_prepareDocEmbeded($classMetadata, $doc); + } + } elseif ($mapping['type'] == 'one') { + $value = $this->_prepareDocEmbeded($classMetadata, $rawValue); + } + } elseif (isset($mapping['reference'])) { + if ($mapping['type'] == 'many') { + $value = array(); + foreach ($rawValue as $doc) { + $value[] = $this->_prepareDocReference($classMetadata, $doc); + } + } else { + $value = $this->_prepareDocReference($classMetadata, $rawValue); + } + } + } else { + $value = Type::getType($mapping['type'])->convertToDatabaseValue($rawValue); + } + $changeset[$mapping['fieldName']] = $value; + } + return $changeset; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Proxy/Proxy.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Proxy/Proxy.php new file mode 100644 index 00000000000..682cf810c58 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Proxy/Proxy.php @@ -0,0 +1,31 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Proxy; + +/** + * Document Proxy interface. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @since 2.0 + * @author Jonathan H. Wage + * @author Roman Borschel + * @author Giorgio Sironi + */ +interface Proxy {} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Proxy/ProxyException.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Proxy/ProxyException.php new file mode 100644 index 00000000000..adf7447bb98 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Proxy/ProxyException.php @@ -0,0 +1,53 @@ +. +*/ + +namespace Doctrine\ODM\MongoDB\Proxy; + +use Doctrine\ODM\MongoDB\MongoDBException; + +/** + * MongoDB ODM Proxy Exception + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 1.0 + * @author Benjamin Eberlei + * @author Jonathan H. Wage + * @author Roman Borschel + * @author Giorgio Sironi + */ +class ProxyException extends MongoDBException +{ + public static function proxyDirectoryRequired() + { + return new self("You must configure a proxy directory. See docs for details"); + } + + public static function proxyNamespaceRequired() + { + return new self("You must configure a proxy namespace. See docs for details"); + } + + public static function proxyDirectoryMustExist() + { + return new self("You must create a proxy directory specified"); + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Proxy/ProxyFactory.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Proxy/ProxyFactory.php new file mode 100644 index 00000000000..506cd5ebe07 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Proxy/ProxyFactory.php @@ -0,0 +1,286 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Proxy; + +use Doctrine\ODM\MongoDB\DocumentManager, + Doctrine\ODM\MongoDB\Mapping\ClassMetadata, + Doctrine\ODM\MongoDB\Mapping\AssociationMapping; + + +/** + * This factory is used to create proxy objects for documents at runtime. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + * @author Giorgio Sironi + */ +class ProxyFactory +{ + /** The DocumentManager this factory is bound to. */ + private $_dm; + /** Whether to automatically (re)generate proxy classes. */ + private $_autoGenerate; + /** The namespace that contains all proxy classes. */ + private $_proxyNamespace; + /** The directory that contains all proxy classes. */ + private $_proxyDir; + + /** + * Initializes a new instance of the ProxyFactory class that is + * connected to the given DocumentManager. + * + * @param DocumentManager $dm The DocumentManager the new factory works for. + * @param string $proxyDir The directory to use for the proxy classes. It must exist. + * @param string $proxyNs The namespace to use for the proxy classes. + * @param boolean $autoGenerate Whether to automatically generate proxy classes. + */ + public function __construct(DocumentManager $dm, $proxyDir, $proxyNs, $autoGenerate = false) + { + if ( ! $proxyDir) { + throw ProxyException::proxyDirectoryRequired(); + } + if ( ! $proxyNs) { + throw ProxyException::proxyNamespaceRequired(); + } + $this->_dm = $dm; + $this->_proxyDir = $proxyDir; + $this->_autoGenerate = $autoGenerate; + $this->_proxyNamespace = $proxyNs; + } + + /** + * Gets a reference proxy instance for the document of the given type and identified by + * the given identifier. + * + * @param string $className + * @param mixed $identifier + * @return object + */ + public function getProxy($className, $identifier) + { + $proxyClassName = str_replace('\\', '', $className) . 'Proxy'; + $fqn = $this->_proxyNamespace . '\\' . $proxyClassName; + + if ($this->_autoGenerate && ! class_exists($fqn, false)) { + $fileName = $this->_proxyDir . DIRECTORY_SEPARATOR . $proxyClassName . '.php'; + $this->_generateProxyClass($this->_dm->getClassMetadata($className), $proxyClassName, $fileName, self::$_proxyClassTemplate); + require $fileName; + } + + if ( ! $this->_dm->getMetadataFactory()->hasMetadataFor($fqn)) { + $this->_dm->getMetadataFactory()->setMetadataFor($fqn, $this->_dm->getClassMetadata($className)); + } + + return new $fqn($this->_dm, $identifier); + } + + /** + * Generates proxy classes for all given classes. + * + * @param array $classes The classes (ClassMetadata instances) for which to generate proxies. + * @param string $toDir The target directory of the proxy classes. If not specified, the + * directory configured on the Configuration of the DocumentManager used + * by this factory is used. + */ + public function generateProxyClasses(array $classes, $toDir = null) + { + $proxyDir = $toDir ?: $this->_proxyDir; + $proxyDir = rtrim($proxyDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + foreach ($classes as $class) { + $proxyClassName = str_replace('\\', '', $class->name) . 'Proxy'; + $proxyFileName = $proxyDir . $proxyClassName . '.php'; + $this->_generateProxyClass($class, $proxyClassName, $proxyFileName, self::$_proxyClassTemplate); + } + } + + /** + * Generates a proxy class file. + * + * @param $class + * @param $originalClassName + * @param $proxyClassName + * @param $file The path of the file to write to. + */ + private function _generateProxyClass($class, $proxyClassName, $fileName, $file) + { + $methods = $this->_generateMethods($class); + $sleepImpl = $this->_generateSleep($class); + + $placeholders = array( + '', + '', '', + '', '' + ); + + if(substr($class->name, 0, 1) == "\\") { + $className = substr($class->name, 1); + } else { + $className = $class->name; + } + + $replacements = array( + $this->_proxyNamespace, + $proxyClassName, $className, + $methods, $sleepImpl + ); + + $file = str_replace($placeholders, $replacements, $file); + + file_put_contents($fileName, $file); + } + + /** + * Generates the methods of a proxy class. + * + * @param ClassMetadata $class + * @return string The code of the generated methods. + */ + private function _generateMethods(ClassMetadata $class) + { + $methods = ''; + + foreach ($class->reflClass->getMethods() as $method) { + /* @var $method ReflectionMethod */ + if ($method->isConstructor() || strtolower($method->getName()) == "__sleep") { + continue; + } + + if ($method->isPublic() && ! $method->isFinal() && ! $method->isStatic()) { + $methods .= PHP_EOL . ' public function ' . $method->getName() . '('; + $firstParam = true; + $parameterString = $argumentString = ''; + + foreach ($method->getParameters() as $param) { + if ($firstParam) { + $firstParam = false; + } else { + $parameterString .= ', '; + $argumentString .= ', '; + } + + // We need to pick the type hint class too + if (($paramClass = $param->getClass()) !== null) { + $parameterString .= '\\' . $paramClass->getName() . ' '; + } elseif ($param->isArray()) { + $parameterString .= 'array '; + } + + if ($param->isPassedByReference()) { + $parameterString .= '&'; + } + + $parameterString .= '$' . $param->getName(); + $argumentString .= '$' . $param->getName(); + + if ($param->isDefaultValueAvailable()) { + $parameterString .= ' = ' . var_export($param->getDefaultValue(), true); + } + } + + $methods .= $parameterString . ')'; + $methods .= PHP_EOL . ' {' . PHP_EOL; + $methods .= ' $this->_load();' . PHP_EOL; + $methods .= ' return parent::' . $method->getName() . '(' . $argumentString . ');'; + $methods .= PHP_EOL . ' }' . PHP_EOL; + } + } + + return $methods; + } + + /** + * Generates the code for the __sleep method for a proxy class. + * + * @param $class + * @return string + */ + private function _generateSleep(ClassMetadata $class) + { + $sleepImpl = ''; + + if ($class->reflClass->hasMethod('__sleep')) { + $sleepImpl .= 'return parent::__sleep();'; + } else { + $sleepImpl .= 'return array('; + $first = true; + + foreach ($class->getReflectionProperties() as $name => $prop) { + if ($first) { + $first = false; + } else { + $sleepImpl .= ', '; + } + + $sleepImpl .= "'" . $name . "'"; + } + + $sleepImpl .= ');'; + } + + return $sleepImpl; + } + + /** Proxy class code template */ + private static $_proxyClassTemplate = +'; + +/** + * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE. + */ +class extends \ implements \Doctrine\ODM\MongoDB\Proxy\Proxy +{ + public $__dm; + public $__identifier; + public $__isInitialized__ = false; + + public function __construct($dm, $identifier) + { + $this->__dm = $dm; + $this->__identifier = $identifier; + $this->__dm->getClassMetadata(__CLASS__)->setIdentifierValue($this, $this->__identifier); + } + + private function _load() + { + if ( ! $this->__isInitialized__ && $this->__dm) { + $this->__isInitialized__ = true; + if ($this->__dm->loadByID(get_class($this), $this->__identifier) === null) { + throw \Doctrine\ODM\MongoDB\MongoDBException::documentNotFound(get_class($this), $this->__identifier); + } + unset($this->__dm); + unset($this->__identifier); + } + } + + + + public function __sleep() + { + if ( ! $this->__isInitialized__) { + throw new \RuntimeException("Not fully loaded proxy can not be serialized."); + } + + } +}'; +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Query.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Query.php new file mode 100644 index 00000000000..752b1db6cec --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Query.php @@ -0,0 +1,1016 @@ +. + */ + +namespace Doctrine\ODM\MongoDB; + +use Doctrine\ODM\MongoDB\DocumentManager, + Doctrine\ODM\MongoDB\Hydrator; + +/** + * Query object that represents a query using a documents MongoCollection::find() + * method. Offers a fluent chainable interface similar to the Doctrine ORM. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @since 1.0 + * @author Jonathan H. Wage + */ +class Query +{ + const TYPE_FIND = 1; + const TYPE_INSERT = 2; + const TYPE_UPDATE = 3; + const TYPE_REMOVE = 4; + const TYPE_GROUP = 5; + + /** The DocumentManager instance for this query */ + private $_dm; + + /** The Document class name being queried */ + private $_className; + + /** The ClassMetadata instance for the class being queried */ + private $_class; + + /** Array of fields to select */ + private $_select = array(); + + /** Array of criteria to query for */ + private $_where = array(); + + /** Array to pass to MongoCollection::update() 2nd argument */ + private $_newObj = array(); + + /** Array of sort options */ + private $_sort = array(); + + /** Limit number of records */ + private $_limit = null; + + /** Skip a specified number of records (offset) */ + private $_skip = null; + + /** Group information. */ + private $_group = array(); + + /** Pass hints to the MongoCursor */ + private $_hints = array(); + + /** Pass immortal to cursor */ + private $_immortal = false; + + /** Pass snapshot to cursor */ + private $_snapshot = false; + + /** Pass slaveOkaye to cursor */ + private $_slaveOkay = false; + + /** Whether or not to try and hydrate the returned data */ + private $_hydrate = true; + + /** Map reduce information */ + private $_mapReduce = array(); + + /** The type of query */ + private $_type = self::TYPE_FIND; + + /** + * Mongo command prefix + * @var string + */ + private $_cmd; + + /** Refresh hint */ + const HINT_REFRESH = 1; + + /** + * Create a new MongoDB Query. + * + * @param DocumentManager $dm + * @param string $className + */ + public function __construct(DocumentManager $dm, $className = null) + { + $this->_dm = $dm; + $this->_hydrator = $dm->getHydrator(); + if ($className !== null) { + $this->_className = $className; + $this->_class = $this->_dm->getClassMetadata($className); + } + $this->_cmd = $dm->getConfiguration()->getMongoCmd(); + } + + /** + * Returns the DocumentManager instance for this query. + * + * @return Doctrine\ODM\MongoDB\DocumentManager $dm + */ + public function getDocumentManager() + { + return $this->_dm; + } + + /** + * Get the type of this query. + * + * @return string $type + */ + public function getType() + { + return $this->_type; + } + + /** + * Whether or not to hydrate the data into objects or to return the raw results + * from mongo. + * + * @param boolean $bool + */ + public function hydrate($bool) + { + $this->_hydrate = $bool; + return $this; + } + + /** + * Set slave okaye. + * + * @param bool $bool + * @return Query + */ + public function slaveOkay($bool = true) + { + $this->_slaveOkay = $bool; + return $this; + } + + /** + * Set snapshot. + * + * @param bool $bool + * @return Query + */ + public function snapshot($bool = true) + { + $this->_snapshot = $bool; + return $this; + } + + /** + * Set immortal. + * + * @param bool $bool + * @return Query + */ + public function immortal($bool = true) + { + $this->_immortal = $bool; + return $this; + } + + /** + * Pass a hint to the MongoCursor + * + * @param string $keyPattern + * @return Query + */ + public function hint($keyPattern) + { + $this->_hints[] = $keyPattern; + return $this; + } + + /** + * Set the Document class being queried. + * + * @param string $className The Document class being queried. + * @return Query + */ + public function from($className) + { + if ($className !== null) { + $this->_className = $className; + $this->_class = $this->_dm->getClassMetadata($className); + } + $this->_type = self::TYPE_FIND; + return $this; + } + + /** + * Proxy method to from() to match mongo naming. + * + * @param string $className + * @return Query + */ + public function find($className = null) + { + return $this->from($className); + } + + /** + * Sets the query as an update query for the given class name or changes + * the type for the current class. + * + * @param string $className + * @return Query + */ + public function update($className = null) + { + if ($className !== null) { + $this->_className = $className; + $this->_class = $this->_dm->getClassMetadata($className); + } + $this->_type = self::TYPE_UPDATE; + return $this; + } + + /** + * Sets the query as an insert query for the given class name or change + * the type for the current class. + * + * @param string $className + * @return Query + */ + public function insert($className = null) + { + if ($className !== null) { + $this->_className = $className; + $this->_class = $this->_dm->getClassMetadata($className); + } + $this->_type = self::TYPE_INSERT; + return $this; + } + + /** + * Sets the query as a remove query for the given class name or changes + * the type for the current class. + * + * @param string $className + * @return Query + */ + public function remove($className = null) + { + if ($className !== null) { + $this->_className = $className; + $this->_class = $this->_dm->getClassMetadata($className); + } + $this->_type = self::TYPE_REMOVE; + return $this; + } + + /** + * Perform an operation similar to SQL's GROUP BY command + * + * @param array $keys + * @param array $initial + * @param string $reduce + * @param array $condition + * @return Query + */ + public function group($keys, array $initial) + { + $this->_group = array( + 'keys' => $keys, + 'initial' => $initial + ); + $this->_type = self::TYPE_GROUP; + return $this; + } + + /** + * The fields to select. + * + * @param string $fieldName + * @return Query + */ + public function select($fieldName = null) + { + $select = func_get_args(); + foreach ($select as $fieldName) { + $this->_select[] = $fieldName; + } + return $this; + } + + public function selectSlice($fieldName, $skip, $limit = null) + { + $slice = array($skip); + if ($limit !== null) { + $slice[] = $limit; + } + return $this->_select[$fieldName][$this->_cmd . 'slice'] = $slice; + } + + /** + * Add a new where criteria erasing all old criteria. + * + * @param string $fieldName + * @param string $value + * @return Query + */ + public function where($fieldName, $value, array $options = array()) + { + $value = $this->_prepareWhereValue($fieldName, $value); + + if (isset($options['elemMatch'])) { + return $this->whereElemMatch($fieldName, $value, $options); + } + + if (isset($options['not'])) { + return $this->whereNot($fieldName, $value, $options); + } + + if (isset($this->_where[$fieldName])) { + $this->_where[$fieldName] = array_merge_recursive($this->_where[$fieldName], $value); + } else { + $this->_where[$fieldName] = $value; + } + + return $this; + } + + public function whereElemMatch($fieldName, $value, array $options = array()) + { + $e = explode('.', $fieldName); + $fieldName = array_pop($e); + $embeddedPath = implode('.', $e); + $this->_where[$embeddedPath][$this->_cmd . 'elemMatch'][$fieldName] = $value; + return $this; + } + + public function whereElemMatchOperator($fieldName, $operator, $value) + { + $e = explode('.', $fieldName); + $fieldName = array_pop($e); + $embeddedPath = implode('.', $e); + $this->_where[$embeddedPath][$this->_cmd . 'elemMatch'][$fieldName][$operator] = $value; + return $this; + } + + public function whereOperator($fieldName, $operator, $value, array $options = array()) + { + if (isset($options['elemMatch'])) { + return $this->whereElemMatchOperator($fieldName, $operator, $value); + } + if (isset($options['not'])) { + $this->_where[$fieldName][$this->_cmd . 'not'][$operator] = $value; + return $this; + } + $this->_where[$fieldName][$operator] = $value; + return $this; + } + + public function whereNot($fieldName, $value, array $options = array()) + { + return $this->whereOperator($fieldName, $this->_cmd . 'not', $value); + } + + /** + * Add a new where in criteria. + * + * @param string $fieldName + * @param mixed $values + * @param array $options + * @return Query + */ + public function whereIn($fieldName, $values, array $options = array()) + { + return $this->whereOperator($fieldName, $this->_cmd . 'in', $values, $options); + } + + /** + * Add where not in criteria. + * + * @param string $fieldName + * @param mixed $values + * @param array $options + * @return Query + */ + public function whereNotIn($fieldName, $values, array $options = array()) + { + return $this->whereOperator($fieldName, $this->_cmd . 'nin', (array) $values, $options); + } + + /** + * Add where not equal criteria. + * + * @param string $fieldName + * @param string $value + * @param array $options + * @return Query + */ + public function whereNotEqual($fieldName, $value, array $options = array()) + { + return $this->whereOperator($fieldName, $this->_cmd . 'ne', $value, $options); + } + + /** + * Add where greater than criteria. + * + * @param string $fieldName + * @param string $value + * @param array $options + * @return Query + */ + public function whereGt($fieldName, $value, array $options = array()) + { + return $this->whereOperator($fieldName, $this->_cmd . 'gt', $value, $options); + } + + /** + * Add where greater than or equal to criteria. + * + * @param string $fieldName + * @param string $value + * @param array $options + * @return Query + */ + public function whereGte($fieldName, $value, array $options = array()) + { + return $this->whereOperator($fieldName, $this->_cmd . 'gte', $value, $options); + } + + /** + * Add where less than criteria. + * + * @param string $fieldName + * @param string $value + * @param array $options + * @return Query + */ + public function whereLt($fieldName, $value, array $options = array()) + { + return $this->whereOperator($fieldName, $this->_cmd . 'lt', $value, $options); + } + + /** + * Add where less than or equal to criteria. + * + * @param string $fieldName + * @param string $value + * @param array $options + * @return Query + */ + public function whereLte($fieldName, $value, array $options = array()) + { + return $this->whereOperator($fieldName, $this->_cmd . 'lte', $value, $options); + } + + /** + * Add where range criteria. + * + * @param string $fieldName + * @param string $start + * @param string $end + * @param array $options + * @return Query + */ + public function whereRange($fieldName, $start, $end, array $options = array()) + { + return $this->whereOperator($fieldName, $this->_cmd . 'gt', $start, $options) + ->whereOperator($fieldName, $this->_cmd . 'lt', $end, $options); + } + + /** + * Add where size criteria. + * + * @param string $fieldName + * @param string $size + * @param array $options + * @return Query + */ + public function whereSize($fieldName, $size, array $options = array()) + { + return $this->whereOperator($fieldName, $this->_cmd . 'size', $size, $options); + } + + /** + * Add where exists criteria. + * + * @param string $fieldName + * @param string $bool + * @param array $options + * @return Query + */ + public function whereExists($fieldName, $bool, array $options = array()) + { + return $this->whereOperator($fieldName, $this->_cmd . 'exists', $bool, $options); + } + + /** + * Add where type criteria. + * + * @param string $fieldName + * @param string $type + * @param array $options + * @return Query + */ + public function whereType($fieldName, $type, array $options = array()) + { + $map = array( + 'double' => 1, + 'string' => 2, + 'object' => 3, + 'array' => 4, + 'binary' => 5, + 'undefined' => 6, + 'objectid' => 7, + 'boolean' => 8, + 'date' => 9, + 'null' => 10, + 'regex' => 11, + 'jscode' => 13, + 'symbol' => 14, + 'jscodewithscope' => 15, + 'integer32' => 16, + 'timestamp' => 17, + 'integer64' => 18, + 'minkey' => 255, + 'maxkey' => 127 + ); + if (is_string($type) && isset($map[$type])) { + $type = $map[$type]; + } + return $this->whereOperator($fieldName, $this->_cmd . 'type', $type, $options); + } + + /** + * Add where all criteria. + * + * @param string $fieldName + * @param mixed $values + * @param array $options + * @return Query + */ + public function whereAll($fieldName, $values, array $options = array()) + { + return $this->whereOperator($fieldName, $this->_cmd . 'all', (array) $values, $options); + } + + /** + * Add where mod criteria. + * + * @param string $fieldName + * @param string $mod + * @param array $options + * @return Query + */ + public function whereMod($fieldName, $mod, array $options = array()) + { + return $this->whereOperator($fieldName, $this->_cmd . 'mod', $mod, $options); + } + + /** + * Set sort and erase all old sorts. + * + * @param string $fieldName + * @param string $order + * @return Query + */ + public function sort($fieldName, $order) + { + $this->_sort[$fieldName] = strtolower($order) === 'asc' ? 1 : -1; + return $this; + } + + /** + * Set the Document limit for the MongoCursor + * + * @param string $limit + * @return Query + */ + public function limit($limit) + { + $this->_limit = $limit; + return $this; + } + + /** + * Set the number of Documents to skip for the MongoCursor + * + * @param string $skip + * @return Query + */ + public function skip($skip) + { + $this->_skip = $skip; + return $this; + } + + /** + * Specify a map reduce operation for this query. + * + * @param mixed $map + * @param mixed $reduce + * @param array $options + * @return Query + */ + public function mapReduce($map, $reduce, array $options = array()) + { + $this->_mapReduce = array( + 'map' => $map, + 'reduce' => $reduce, + 'options' => $options + ); + return $this; + } + + /** + * Specify a map operation for this query. + * + * @param string $map + * @return Query + */ + public function map($map) + { + $this->_mapReduce['map'] = $map; + return $this; + } + + /** + * Specify a reduce operation for this query. + * + * @param string $reduce + * @return Query + */ + public function reduce($reduce) + { + $this->_mapReduce['reduce'] = $reduce; + return $this; + } + + /** + * Specify the map reduce array of options for this query. + * + * @param array $options + * @return Query + */ + public function mapReduceOptions(array $options) + { + $this->_mapReduce['options'] = $options; + return $this; + } + + /** + * Set field to value. + * + * @param string $name + * @param mixed $value + * @param boolean $atomic + * @return Query + */ + public function set($name, $value, $atomic = true) + { + if ($atomic === true) { + $this->_newObj[$this->_cmd . 'set'][$name] = $value; + } else { + if (strpos($name, '.') !== false) { + $e = explode('.', $name); + $current = &$this->_newObj; + foreach ($e as $v) { + $current[$v] = null; + $current = &$current[$v]; + } + $current = $value; + } else { + $this->_newObj[$name] = $value; + } + } + return $this; + } + + /** + * Set the $newObj array + * + * @param array $newObj + */ + public function setNewObj($newObj) + { + $this->_newObj = $newObj; + return $this; + } + + /** + * Increment field by the number value if field is present in the document, + * otherwise sets field to the number value. + * + * @param string $name + * @param integer $value + * @return Query + */ + public function inc($name, $value) + { + $this->_newObj[$this->_cmd . 'inc'][$name] = $value; + return $this; + } + + /** + * Deletes a given field. + * + * @param string $field + * @return Query + */ + public function unsetField($field) + { + $this->_newObj[$this->_cmd . 'unset'][$field] = 1; + return $this; + } + + /** + * Appends value to field, if field is an existing array, otherwise sets + * field to the array [value] if field is not present. If field is present + * but is not an array, an error condition is raised. + * + * @param string $field + * @param mixed $value + * @return Query + */ + public function push($field, $value) + { + $this->_newObj[$this->_cmd . 'push'][$field] = $value; + return $this; + } + + /** + * Appends each value in valueArray to field, if field is an existing + * array, otherwise sets field to the array valueArray if field is not + * present. If field is present but is not an array, an error condition is + * raised. + * + * @param string $field + * @param array $valueArray + * @return Query + */ + public function pushAll($field, array $valueArray) + { + $this->_newObj[$this->_cmd . 'pushAll'][$field] = $valueArray; + return $this; + } + + /** + * Adds value to the array only if its not in the array already. + * + * @param string $field + * @param mixed $value + * @return Query + */ + public function addToSet($field, $value) + { + $this->_newObj[$this->_cmd . 'addToSet'][$field] = $value; + return $this; + } + + /** + * Adds values to the array only they are not in the array already. + * + * @param string $field + * @param array $values + * @return Query + */ + public function addManyToSet($field, array $values) + { + if ( ! isset($this->_newObj[$this->_cmd . 'addToSet'][$field])) { + $this->_newObj[$this->_cmd . 'addToSet'][$field][$this->_cmd . 'each'] = array(); + } + if ( ! is_array($this->_newObj[$this->_cmd . 'addToSet'][$field])) { + $this->_newObj[$this->_cmd . 'addToSet'][$field] = array($this->_cmd . 'each' => array($this->_newObj[$this->_cmd . 'addToSet'][$field])); + } + $this->_newObj[$this->_cmd . 'addToSet'][$field][$this->_cmd . 'each'] = array_merge_recursive($this->_newObj[$this->_cmd . 'addToSet'][$field][$this->_cmd . 'each'], $values); + } + + /** + * Removes first element in an array + * + * @param string $field The field name + * @return Query + */ + public function popFirst($field) + { + $this->_newObj[$this->_cmd . 'pop'][$field] = 1; + return $this; + } + + /** + * Removes last element in an array + * + * @param string $field The field name + * @return Query + */ + public function popLast($field) + { + $this->_newObj[$this->_cmd . 'pop'][$field] = -1; + return $this; + } + + /** + * Removes all occurrences of value from field, if field is an array. + * If field is present but is not an array, an error condition is raised. + * + * @param string $field + * @param mixed $value + * @return Query + */ + public function pull($field, $value) + { + $this->_newObj[$this->_cmd . 'pull'][$field] = $value; + return $this; + } + + /** + * Removes all occurrences of each value in value_array from field, if + * field is an array. If field is present but is not an array, an error + * condition is raised. + * + * @param string $field + * @param array $valueArray + * @return Query + */ + public function pullAll($field, array $valueArray) + { + $this->_newObj[$this->_cmd . 'pullAll'][$field] = $valueArray; + return $this; + } + + /** + * Proxy to execute() method + * + * @param array $options + * @return Query + */ + public function getResult(array $options = array()) + { + return $this->execute($options); + } + + /** + * Execute the query and return an array of results + * + * @param array $options + * @return mixed $result The result of the query. + */ + public function execute(array $options = array()) + { + switch ($this->_type) { + case self::TYPE_FIND; + return $this->getCursor()->getResults(); + break; + + case self::TYPE_REMOVE; + return $this->_dm->getDocumentCollection($this->_className) + ->remove($this->_where, $options); + break; + + case self::TYPE_UPDATE; + return $this->_dm->getDocumentCollection($this->_className) + ->update($this->_where, $this->_newObj, $options); + break; + + case self::TYPE_INSERT; + return $this->_dm->getDocumentCollection($this->_className) + ->insert($this->_newObj); + break; + + case self::TYPE_GROUP; + return $this->_dm->getDocumentCollection($this->_className) + ->group( + $this->_group['keys'], $this->_group['initial'], + $this->_mapReduce['reduce'], $this->_where + ); + break; + } + } + + /** + * Count the number of results for this query. + * + * @param bool $all + * @return integer $count + */ + public function count($all = false) + { + return $this->getCursor()->count($all); + } + + /** + * Execute the query and get a single result + * + * @return object $document The single document. + */ + public function getSingleResult() + { + return $this->getCursor()->getSingleResult(); + } + + /** + * Get the MongoCursor for this query instance. + * + * @return MongoCursor $cursor + */ + public function getCursor() + { + if ($this->_type !== self::TYPE_FIND) { + throw new \InvalidArgumentException( + 'Cannot get cursor for an update or remove query. Use execute() method.' + ); + } + + if (isset($this->_mapReduce['map']) && $this->_mapReduce['reduce']) { + $cursor = $this->_dm->mapReduce($this->_className, $this->_mapReduce['map'], $this->_mapReduce['reduce'], $this->_where, isset($this->_mapReduce['options']) ? $this->_mapReduce['options'] : array()); + $cursor->hydrate(false); + } else { + if (isset($this->_mapReduce['reduce'])) { + $this->_where[$this->_cmd . 'where'] = $this->_mapReduce['reduce']; + } + $cursor = $this->_dm->find($this->_className, $this->_where, $this->_select); + $cursor->hydrate($this->_hydrate); + } + $cursor->limit($this->_limit); + $cursor->skip($this->_skip); + $cursor->sort($this->_sort); + $cursor->immortal($this->_immortal); + $cursor->slaveOkay($this->_slaveOkay); + if ($this->_snapshot) { + $cursor->snapshot(); + } + foreach ($this->_hints as $keyPattern) { + $cursor->hint($keyPattern); + } + return $cursor; + } + + /** + * Iterator over the query using the MongoCursor. + * + * @return MongoCursor $cursor + */ + public function iterate() + { + return $this->getCursor(); + } + + /** + * Gets an array of information about this query for debugging. + * + * @param string $name + * @return array $debug + */ + public function debug($name = null) + { + $debug = array( + 'className' => $this->_className, + 'type' => $this->_type, + 'select' => $this->_select, + 'where' => $this->_where, + 'newObj' => $this->_newObj, + 'sort' => $this->_sort, + 'limit' => $this->_limit, + 'skip' => $this->_skip, + 'group' => $this->_group, + 'hints' => $this->_hints, + 'immortal' => $this->_immortal, + 'snapshot' => $this->_snapshot, + 'slaveOkay' => $this->_slaveOkay, + 'hydrate' => $this->_hydrate, + 'mapReduce' => $this->_mapReduce + ); + if ($name !== null) { + return $debug[$name]; + } + foreach ($debug as $key => $value) { + if ( ! $value) { + unset($debug[$key]); + } + } + return $debug; + } + + private function _prepareWhereValue(&$fieldName, $value) + { + if ($fieldName === $this->_class->identifier) { + $fieldName = '_id'; + if (is_array($value)) { + foreach ($value as $k => $v) { + $value[$k] = $this->_class->getDatabaseIdentifierValue($v); + } + } else { + $value = $this->_class->getDatabaseIdentifierValue($value); + } + } + return $value; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Query/Lexer.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Query/Lexer.php new file mode 100644 index 00000000000..42884ab57b2 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Query/Lexer.php @@ -0,0 +1,172 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Query; + +/** + * Scans a MongoDB DQL query for tokens. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @since 1.0 + * @author Guilherme Blanco + * @author Janne Vanhala + * @author Roman Borschel + * @author Jonathan H. Wage + */ +class Lexer extends \Doctrine\Common\Lexer +{ + // All tokens that are not valid identifiers must be < 100 + const T_NONE = 1; + const T_INTEGER = 2; + const T_STRING = 3; + const T_INPUT_PARAMETER = 4; + const T_FLOAT = 5; + const T_COMMA = 6; + const T_DIVIDE = 7; + const T_DOT = 8; + const T_EQUALS = 9; + const T_NOT_EQUALS = 10; + const T_GT = 11; + const T_LT = 12; + const T_LTE = 13; + const T_GTE = 14; + const T_OPEN_BRACKET = 15; + const T_CLOSE_BRACKET = 16; + const T_OPEN_CURLY_BRACE = 17; + const T_CLOSE_CURLY_BRACE = 18; + const T_OPEN_PARENTHESES = 19; + const T_CLOSE_PARENTHESES = 20; + + // All tokens that are also identifiers should be >= 100 + const T_IDENTIFIER = 100; + const T_FIND = 101; + const T_UPDATE = 102; + const T_INSERT = 103; + const T_REMOVE = 104; + const T_GROUP = 105; + const T_IN = 106; + const T_NOTIN = 107; + const T_MOD = 108; + const T_ALL = 109; + const T_SIZE = 110; + const T_EXISTS = 111; + const T_TYPE = 112; + const T_SORT = 113; + const T_LIMIT = 114; + const T_SKIP = 115; + const T_SELECT = 116; + const T_SET = 117; + const T_UNSET = 118; + const T_INC = 119; + const T_PUSH = 120; + const T_PUSHALL = 121; + const T_PULL = 122; + const T_PULLALL = 123; + const T_ADDTOSET = 124; + const T_ADDMANYTOSET = 125; + const T_POPFIRST = 126; + const T_POPLAST = 127; + const T_WHERE = 128; + const T_REDUCE = 129; + const T_MAP = 130; + const T_AND = 131; + const T_OR = 132; + const T_TRUE = 133; + const T_FALSE = 134; + const T_ANY = 135; + const T_NOT = 136; + + /** + * @inheritdoc + */ + protected function getCatchablePatterns() + { + return array( + '[a-z_][a-z0-9_\:\\\]*[a-z0-9_]{1}', + '(?:[0-9]+(?:[\.][0-9]+)*)(?:e[+-]?[0-9]+)?', + "'(?:[^']|'')*'", + '\?[1-9][0-9]*|:[a-z][a-z0-9_]+' + ); + } + + /** + * @inheritdoc + */ + protected function getNonCatchablePatterns() + { + return array('\s+'); + } + + /** + * @inheritdoc + */ + protected function _getType(&$value) + { + $type = self::T_NONE; + + // Recognizing numeric values + if (is_numeric($value)) { + $type = (strpos($value, '.') !== false || stripos($value, 'e') !== false) + ? self::T_FLOAT : self::T_INTEGER; + if ($type === self::T_INTEGER) { + $value = (integer) $value; + } + return $type; + } + + // Differentiate between quoted names, identifiers, input parameters and symbols + if ($value[0] === "'") { + $value = str_replace("''", "'", substr($value, 1, strlen($value) - 2)); + return self::T_STRING; + } else if (ctype_alpha($value[0]) || $value[0] === '_') { + $name = 'Doctrine\ODM\MongoDB\Query\Lexer::T_' . strtoupper($value); + if (defined($name)) { + $type = constant($name); + if ($type > 100) { + return $type; + } + } + return self::T_IDENTIFIER; + } else if ($value[0] === ':' || $value[0] === '?') { + return self::T_INPUT_PARAMETER; + } else { + switch ($value) { + case '{': return self::T_OPEN_CURLY_BRACE; + case '}': return self::T_CLOSE_CURLY_BRACE; + case '[': return self::T_OPEN_BRACKET; + case ']': return self::T_CLOSE_BRACKET; + case '(': return self::T_OPEN_PARENTHESES; + case ')': return self::T_CLOSE_PARENTHESES; + case '.': return self::T_DOT; + case ',': return self::T_COMMA; + case '<=': return self::T_GTE; + case '>=': return self::T_LTE; + case '=': return self::T_EQUALS; + case '!=': return self::T_NOT_EQUALS; + case '>': return self::T_GT; + case '<': return self::T_LT; + default: + // Do nothing + break; + } + } + + return $type; + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Query/Parser.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Query/Parser.php new file mode 100644 index 00000000000..3bf943ebc13 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Query/Parser.php @@ -0,0 +1,630 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Query; + +use Doctrine\ODM\MongoDB\Query, + Doctrine\ODM\MongoDB\DocumentManager; + +/** + * A simple parser for MongoDB Document Query Language + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @since 1.0 + * @author Jonathan H. Wage + */ +class Parser +{ + /** + * The DocumentManager instance for this query + * + * @var Doctrine\ODM\MongoDB\DocumentManager + */ + private $_dm; + + /** + * The lexer. + * + * @var Doctrine\ODM\MongoDB\Query\Lexer + */ + private $_lexer; + + public function __construct(DocumentManager $dm) + { + $this->_dm = $dm; + $this->_lexer = new Lexer; + } + + public function parse($query, $parameters = array()) + { + if (strpos($query, '?') !== false && strpos($query, ':') !== false) { + throw new \InvalidArgumentException('Cannot mixed named and regular placeholders.'); + } + + $this->_lexer->reset(); + $this->_lexer->setInput($query); + + $query = $this->QueryLanguage($parameters); + + return $query; + } + + /** + * Attempts to match the given token with the current lookahead token. + * If they match, updates the lookahead token; otherwise raises a syntax error. + * + * @param int|string token type or value + * @return bool True if tokens match; false otherwise. + */ + public function match($token) + { + if ( ! ($this->_lexer->lookahead['type'] === $token)) { + $this->syntaxError($this->_lexer->getLiteral($token)); + } + $this->_lexer->moveNext(); + } + + /** + * Generates a new syntax error. + * + * @param string $expected Expected string. + * @param array $token Optional token. + * @throws AnnotationException + */ + public function syntaxError($expected, $token = null) + { + if ($token === null) { + $token = $this->_lexer->lookahead; + } + + $message = "Expected {$expected}, got "; + + if ($this->_lexer->lookahead === null) { + $message .= 'end of string'; + } else { + $message .= "'{$token['value']}' at position {$token['position']}"; + } + + $message .= '.'; + + throw new \Doctrine\ODM\MongoDB\MongoDBException($message); + } + + /** + * QueryLanguage ::= FindQuery | InsertQuery | UpdateQuery | RemoveQuery + */ + public function QueryLanguage(array &$parameters) + { + $this->_lexer->moveNext(); + + $query = new Query($this->_dm); + + switch ($this->_lexer->lookahead['type']) { + case Lexer::T_FIND: + $this->FindQuery($query, $parameters); + break; + case Lexer::T_INSERT: + $this->InsertQuery($query, $parameters); + break; + case Lexer::T_UPDATE: + $this->UpdateQuery($query, $parameters); + break; + case Lexer::T_REMOVE: + $this->RemoveQuery($query, $parameters); + break; + default: + $this->syntaxError('FIND, INSERT, UPDATE or REMOVE'); + break; + } + return $query; + } + + /** + * FindQuery ::= FindClause [WhereClause] [MapClause] [ReduceClause] [SortClause] [LimitClause] [SkipClause] + */ + public function FindQuery(Query $query, array &$parameters) + { + $this->FindClause($query); + + if ($this->_lexer->isNextToken(Lexer::T_WHERE)) { + $this->WhereClause($query, $parameters); + } + + if ($this->_lexer->isNextToken(Lexer::T_MAP)) { + $this->MapClause($query, $parameters); + } + + if ($this->_lexer->isNextToken(Lexer::T_REDUCE)) { + $this->ReduceClause($query, $parameters); + } + + while ($this->_lexer->isNextToken($this->_lexer->lookahead['type'])) { + $this->match($this->_lexer->lookahead['type']); + switch ($this->_lexer->token['type']) { + case Lexer::T_SORT: + $this->SortClause($query, $parameters); + break; + case Lexer::T_SKIP; + $this->SkipClause($query, $parameters); + break; + case Lexer::T_LIMIT: + $this->LimitClause($query, $parameters); + break; + default: + break(2); + } + } + } + + /** + * FindClause ::= "FIND" all | SelectField {"," SelectField} + */ + public function FindClause(Query $query) + { + $this->match(Lexer::T_FIND); + + if ($this->_lexer->isNextToken(Lexer::T_ALL)) { + $this->match(Lexer::T_ALL); + } else { + $this->SelectField($query); + while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { + $this->match(Lexer::T_COMMA); + $this->SelectField($query); + } + } + + $this->match(Lexer::T_IDENTIFIER); + $query->find($this->_lexer->token['value']); + } + + /** + * SelectField ::= DocumentFieldName + */ + public function SelectField(Query $query) + { + $fieldName = $this->DocumentFieldName(); + + $limit = null; + $skip = null; + while ($this->_lexer->isNextToken($this->_lexer->lookahead['type'])) { + switch ($this->_lexer->lookahead['type']) { + case Lexer::T_SKIP; + $this->match(Lexer::T_SKIP); + $this->match(Lexer::T_INTEGER); + $skip = $this->_lexer->token['value']; + break; + case Lexer::T_LIMIT: + $this->match(Lexer::T_LIMIT); + $this->match(Lexer::T_INTEGER); + $limit = $this->_lexer->token['value']; + break; + default: + break(2); + } + } + + if ($skip || $limit) { + $skip = $skip !== null ? $skip : 0; + $query->selectSlice($fieldName, $skip, $limit); + } else { + $query->select($fieldName); + } + } + + /** + * UpdateQuery ::= UpdateClause [WhereClause] + */ + public function UpdateQuery(Query $query, array &$parameters) + { + $this->match(Lexer::T_UPDATE); + $this->match(Lexer::T_IDENTIFIER); + $query->update($this->_lexer->token['value']); + + $this->UpdateClause($query, $parameters); + while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { + $this->match(Lexer::T_COMMA); + $this->UpdateClause($query, $parameters); + } + + if ($this->_lexer->isNextToken(Lexer::T_WHERE)) { + $this->WhereClause($query, $parameters); + } + } + + /** + * UpdateClause ::= [SetExpression], [UnsetExpression], [IncrementExpression], + * [PushExpression], [PushAllExpression], [PullExpression], + * [PullAllExpression], [AddToSetExpression], [AddManyToSetExpression], + * [PopFirstExpression], [PopLastExpression] + */ + public function UpdateClause(Query $query, array &$parameters) + { + $this->match($this->_lexer->lookahead['type']); + switch ($this->_lexer->token['type']) { + case Lexer::T_SET: + $this->SetExpression($query, $parameters); + break; + case Lexer::T_UNSET: + $this->UnsetExpression($query, $parameters); + break; + case Lexer::T_INC: + $this->IncrementExpression($query, $parameters); + break; + case Lexer::T_PUSH: + $this->PushExpression($query, $parameters); + break; + case Lexer::T_PUSHALL: + $this->PushAllExpression($query, $parameters); + break; + case Lexer::T_PULL: + $this->PullExpression($query, $parameters); + break; + case Lexer::T_PULLALL: + $this->PullAllExpression($query, $parameters); + break; + case Lexer::T_ADDTOSET: + $this->AddToSetExpression($query, $parameters); + break; + case Lexer::T_ADDMANYTOSET: + $this->AddManyToSetExpression($query, $parameters); + break; + case Lexer::T_POPFIRST: + $this->PopFirstExpression($query, $parameters); + break; + case Lexer::T_POPLAST: + $this->PopLastExpression($query, $parameters); + break; + } + } + + /** + * InsertQuery ::= InsertClause InsertSetClause {"," InsertSetClause} + */ + public function InsertQuery(Query $query, array &$parameters) + { + $this->match(Lexer::T_INSERT); + $this->match(Lexer::T_IDENTIFIER); + $query->insert($this->_lexer->token['value']); + + $this->match(Lexer::T_SET); + $this->InsertSetClause($query, $parameters); + while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { + $this->match(Lexer::T_COMMA); + $this->InsertSetClause($query, $parameters); + } + } + + /** + * InsertSetClause ::= DocumentFieldName "=" Value + */ + public function InsertSetClause(Query $query, array &$parameters) + { + $fieldName = $this->DocumentFieldName(); + $value = $this->Value($parameters); + $query->set($fieldName, $value, false); + } + + /** + * RemoveQuery ::= RemoveClause [WhereClause] + * RemoveClause ::= "REMOVE" DocumentClassName + */ + public function RemoveQuery(Query $query, array &$parameters) + { + $this->match(Lexer::T_REMOVE); + $this->match(Lexer::T_IDENTIFIER); + $query->remove($this->_lexer->token['value']); + + if ($this->_lexer->isNextToken(Lexer::T_WHERE)) { + $this->WhereClause($query, $parameters); + } + } + + /** + * SortClause ::= SortClauseField {"," SortClauseField} + */ + public function SortClause(Query $query) + { + $this->SortClauseField($query); + while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { + $this->match(Lexer::T_COMMA); + $this->SortClauseField($query); + } + } + + /** + * SortClauseField ::= DocumentFieldName "ASC | DESC" + */ + public function SortClauseField(Query $query) + { + $fieldName = $this->DocumentFieldName(); + $this->match(Lexer::T_IDENTIFIER); + $order = $this->_lexer->token['value']; + $query->sort($fieldName, $order); + } + + /** + * LimitClause ::= "LIMIT" LimitInteger + */ + public function LimitClause(Query $query) + { + $this->match($this->_lexer->lookahead['type']); + $query->limit($this->_lexer->token['value']); + } + + /** + * SkipClause ::= "SKIP" SkipInteger + */ + public function SkipClause(Query $query) + { + $this->match($this->_lexer->lookahead['type']); + $query->skip($this->_lexer->token['value']); + } + + /** + * MapClause ::= "MAP" MapFunction + */ + public function MapClause(Query $query, array &$parameters) + { + $this->match(Lexer::T_MAP); + $this->match(Lexer::T_STRING); + $query->map($this->_lexer->token['value']); + } + + /** + * ReduceClause ::= "REDUCE" ReduceFunction + */ + public function ReduceClause(Query $query, array &$parameters) + { + $this->match(Lexer::T_REDUCE); + $this->match(Lexer::T_STRING); + $query->reduce($this->_lexer->token['value']); + } + + /** + * DocumentFieldName ::= DocumentFieldName | EmbeddedDocument "." {"." DocumentFieldName} + */ + public function DocumentFieldName() + { + $this->match(Lexer::T_IDENTIFIER); + $fieldName = $this->_lexer->token['value']; + while ($this->_lexer->isNextToken(Lexer::T_DOT)) { + $this->match(Lexer::T_DOT); + $this->match(Lexer::T_IDENTIFIER); + $fieldName .= '.' . $this->_lexer->token['value']; + } + return $fieldName; + } + + /** + * WhereClause ::= "WHERE" WhereClausePart {"AND" WhereClausePart} + */ + public function WhereClause(Query $query, array &$parameters) + { + $this->match(Lexer::T_WHERE); + $this->WhereClauseExpression($query, $parameters); + while ($this->_lexer->isNextToken(Lexer::T_AND)) { + $this->match(Lexer::T_AND); + $this->WhereClauseExpression($query, $parameters); + } + } + + /** + * WhereClausePart ::= ["all", "not"] DocumentFieldName WhereClauseExpression Value + * WhereClauseExpression ::= "=" | "!=" | ">=" | "<=" | ">" | "<" | "in" + * "notIn" | "all" | "size" | "exists" | "type" + */ + public function WhereClauseExpression(Query $query, array &$parameters) + { + $options = array(); + switch ($this->_lexer->lookahead['type']) { + case Lexer::T_ALL: + $this->match(Lexer::T_ALL); + $options['elemMatch'] = true; + break; + case Lexer::T_NOT: + $this->match(Lexer::T_NOT); + $options['not'] = true; + break; + } + + $fieldName = $this->DocumentFieldName(); + + $operator = $this->_lexer->lookahead['value']; + + $value = $this->Value($parameters); + + switch ($operator) { + case '=': + $query->where($fieldName, $value, $options); + break; + case '!=': + $query->whereNotEqual($fieldName, $value, $options); + break; + case '>=': + $query->whereGte($fieldName, $value, $options); + break; + case '<=': + $query->whereLte($fieldName, $value, $options); + break; + case '>': + $query->whereGt($fieldName, $value, $options); + break; + case '<': + $query->whereLt($fieldName, $value, $options); + break; + case 'in': + $query->whereIn($fieldName, $value, $options); + break; + case 'notIn': + $query->whereNotIn($fieldName, $value, $options); + break; + case 'all': + $query->whereAll($fieldName, $value, $options); + break; + case 'size': + $query->whereSize($fieldName, $value, $options); + break; + case 'exists': + $query->whereExists($fieldName, $value, $options); + break; + case 'type': + $query->whereType($fieldName, $value, $options); + break; + case 'mod': + $query->whereMod($fieldName, $value, $options); + break; + default: + $this->syntaxError('Invalid atomic update operator.'); + } + } + + /** + * Value ::= LiteralValue | JsonObject | JsonArray + */ + public function Value(array &$parameters) + { + $this->match($this->_lexer->lookahead['type']); + $this->match($this->_lexer->lookahead['type']); + $value = $this->_lexer->token['value']; + if (isset($parameters[$value])) { + $value = $parameters[$value]; + } + if ($value === '?') { + $value = array_shift($parameters); + } + // detect and decode json values + if ($value[0] === '[' || $value[0] === '{') { + return json_decode($value); + } + if ($value === 'true') { + $value = true; + } + if ($value === 'false') { + $value = false; + } + return $value; + } + + /** + * SetExpression ::= "SET" DocumentFieldName "=" Value {"," SetExpression} + */ + public function SetExpression(Query $query, array &$parameters) + { + $fieldName = $this->DocumentFieldName(); + $value = $this->Value($parameters); + $query->set($fieldName, $value); + } + + /** + * UnsetExpression ::= "UNSET" DocumentFieldName {"," UnsetExpression} + */ + public function UnsetExpression(Query $query, array &$parameters) + { + $this->match(Lexer::T_IDENTIFIER); + $query->unsetField($this->_lexer->token['value']); + } + + /** + * PushExpression ::= "PUSH" DocumentFieldName Value {"," PushExpression} + */ + public function PushExpression(Query $query, array &$parameters) + { + $fieldName = $this->DocumentFieldName(); + $value = $this->Value($parameters); + $query->push($fieldName, $value); + } + + /** + * PushAllExpression ::= "PUSHALL" DocumentFieldName Value {"," PushAllExpression} + */ + public function PushAllExpression(Query $query, array &$parameters) + { + $fieldName = $this->DocumentFieldName(); + $value = $this->Value($parameters); + $query->pushAll($fieldName, $value); + } + + /** + * PullExpression ::= "PULL" DocumentFieldName Value {"," PullExpression} + */ + public function PullExpression(Query $query, array &$parameters) + { + $fieldName = $this->DocumentFieldName(); + $value = $this->Value($parameters); + $query->pull($fieldName, $value); + } + + /** + * PullAllExpression ::= "PULLALL" DocumentFieldName Value {"," PullAllExpression} + */ + public function PullAllExpression(Query $query, array &$parameters) + { + $fieldName = $this->DocumentFieldName(); + $value = $this->Value($parameters); + $query->pullAll($fieldName, $value); + } + + /** + * PopFirstExpression ::= "POPFIRST" DocumentFieldName {"," PopFirstExpression} + */ + public function PopFirstExpression(Query $query, array &$parameters) + { + $this->match(Lexer::T_IDENTIFIER); + $query->popFirst($this->_lexer->token['value']); + } + + /** + * PopLastExpression ::= "POPLAST" DocumentFieldName {"," PopLastExpression} + */ + public function PopLastExpression(Query $query, array &$parameters) + { + $this->match(Lexer::T_IDENTIFIER); + $query->popLast($this->_lexer->token['value']); + } + + /** + * AddToSetExpression ::= "ADDTOSET" DocumentFieldName Value {"," AddToSetExpression} + */ + public function AddToSetExpression(Query $query, array &$parameters) + { + $fieldName = $this->DocumentFieldName(); + $value = $this->Value($parameters); + $query->addToSet($fieldName, $value); + } + + /** + * AddManyToSetExpression ::= "ADDMANYTOSET" DocumentFieldName Value {"," AddManyToSetExpression} + */ + public function AddManyToSetExpression(Query $query, array &$parameters) + { + $fieldName = $this->DocumentFieldName(); + $value = $this->Value($parameters); + $query->addManyToSet($fieldName, $value); + } + + /** + * IncrementExpression ::= "INC" DocumentFieldName "=" IncrementInteger {"," IncrementExpression} + */ + public function IncrementExpression(Query $query, array &$parameters) + { + $fieldName = $this->DocumentFieldName(); + $value = $this->Value($parameters); + $query->inc($fieldName, $value); + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/UnitOfWork.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/UnitOfWork.php new file mode 100644 index 00000000000..f883a2fa070 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/UnitOfWork.php @@ -0,0 +1,1884 @@ +. + */ + +namespace Doctrine\ODM\MongoDB; + +use Doctrine\ODM\MongoDB\DocumentManager, + Doctrine\ODM\MongoDB\Internal\CommitOrderCalculator, + Doctrine\ODM\MongoDB\Mapping\ClassMetadata, + Doctrine\ODM\MongoDB\Proxy\Proxy, + Doctrine\ODM\MongoDB\Mapping\Types\Type, + Doctrine\ODM\MongoDB\Event\LifecycleEventArgs, + Doctrine\Common\Collections\Collection, + Doctrine\Common\Collections\ArrayCollection; + +/** + * The UnitOfWork is responsible for tracking changes to objects during an + * "object-level" transaction and for writing out changes to the database + * in the correct order. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class UnitOfWork +{ + /** + * An document is in MANAGED state when its persistence is managed by an DocumentManager. + */ + const STATE_MANAGED = 1; + + /** + * An document is new if it has just been instantiated (i.e. using the "new" operator) + * and is not (yet) managed by an DocumentManager. + */ + const STATE_NEW = 2; + + /** + * A detached document is an instance with a persistent identity that is not + * (or no longer) associated with an DocumentManager (and a UnitOfWork). + */ + const STATE_DETACHED = 3; + + /** + * A removed document instance is an instance with a persistent identity, + * associated with an DocumentManager, whose persistent state has been + * deleted (or is scheduled for deletion). + */ + const STATE_REMOVED = 4; + + /** + * The identity map that holds references to all managed documents that have + * an identity. The documents are grouped by their class name. + * Since all classes in a hierarchy must share the same identifier set, + * we always take the root class name of the hierarchy. + * + * @var array + */ + private $_identityMap = array(); + + /** + * Map of all identifiers of managed documents. + * Keys are object ids (spl_object_hash). + * + * @var array + */ + private $_documentIdentifiers = array(); + + /** + * Map of the original document data of managed documents. + * Keys are object ids (spl_object_hash). This is used for calculating changesets + * at commit time. + * + * @var array + * @internal Note that PHPs "copy-on-write" behavior helps a lot with memory usage. + * A value will only really be copied if the value in the document is modified + * by the user. + */ + private $_originalDocumentData = array(); + + /** + * Map of document changes. Keys are object ids (spl_object_hash). + * Filled at the beginning of a commit of the UnitOfWork and cleaned at the end. + * + * @var array + */ + private $_documentChangeSets = array(); + + /** + * The (cached) states of any known documents. + * Keys are object ids (spl_object_hash). + * + * @var array + */ + private $_documentStates = array(); + + /** + * Map of documents that are scheduled for dirty checking at commit time. + * This is only used for documents with a change tracking policy of DEFERRED_EXPLICIT. + * Keys are object ids (spl_object_hash). + * + * @var array + */ + private $_scheduledForDirtyCheck = array(); + + /** + * A list of all pending document insertions. + * + * @var array + */ + private $_documentInsertions = array(); + + /** + * A list of all pending document updates. + * + * @var array + */ + private $_documentUpdates = array(); + + /** + * A list of all pending document deletions. + * + * @var array + */ + private $_documentDeletions = array(); + + /** + * List of collections visited during changeset calculation on a commit-phase of a UnitOfWork. + * At the end of the UnitOfWork all these collections will make new snapshots + * of their data. + * + * @var array + */ + private $_visitedCollections = array(); + + /** + * The DocumentManager that "owns" this UnitOfWork instance. + * + * @var Doctrine\ODM\MongoDB\DocumentManager + */ + private $_dm; + + /** + * The calculator used to calculate the order in which changes to + * documents need to be written to the database. + * + * @var Doctrine\ODM\MongoDB\Internal\CommitOrderCalculator + */ + private $_commitOrderCalculator; + + /** + * Orphaned documents that are scheduled for removal. + * + * @var array + */ + private $_orphanRemovals = array(); + + /** + * The EventManager used for dispatching events. + * + * @var EventManager + */ + private $_evm; + + /** + * The Hydrator used for hydrating array Mongo documents to Doctrine object documents. + * + * @var string + */ + private $_hydrator; + + protected $_documentPersisters = array(); + + /** + * Initializes a new UnitOfWork instance, bound to the given DocumentManager. + * + * @param Doctrine\ODM\MongoDB\DocumentManager $dm + */ + public function __construct(DocumentManager $dm) + { + $this->_dm = $dm; + $this->_evm = $dm->getEventManager(); + $this->_hydrator = $dm->getHydrator(); + } + + /** + * Get the document persister instance for the given document name + * + * @param string $documentName + * @return BasicDocumentPersister + */ + public function getDocumentPersister($documentName) + { + if ( ! isset($this->_documentPersisters[$documentName])) { + $class = $this->_dm->getClassMetadata($documentName); + $this->_documentPersisters[$documentName] = new Persisters\BasicDocumentPersister($this->_dm, $class); + } + return $this->_documentPersisters[$documentName]; + } + + /** + * Set the document persister instance to use for the given document name + * + * @param string $documentName + * @param BasicDocumentPersister $persister + */ + public function setDocumentPersister($documentName, Persisters\BasicDocumentPersister $persister) + { + $this->_documentPersisters[$documentName] = $persister; + } + + /** + * Commits the UnitOfWork, executing all operations that have been postponed + * up to this point. The state of all managed documents will be synchronized with + * the database. + * + * The operations are executed in the following order: + * + * 1) All document insertions + * 2) All document updates + * 3) All collection deletions + * 4) All collection updates + * 5) All document deletions + * + */ + public function commit() + { + // Compute changes done since last commit. + $this->computeChangeSets(); + + if ( ! ($this->_documentInsertions || + $this->_documentDeletions || + $this->_documentUpdates || + $this->_orphanRemovals)) { + return; // Nothing to do. + } + + if ($this->_orphanRemovals) { + foreach ($this->_orphanRemovals as $orphan) { + $this->remove($orphan); + } + } + + // Raise onFlush + if ($this->_evm->hasListeners(ODMEvents::onFlush)) { + $this->_evm->dispatchEvent(ODMEvents::onFlush, new Event\OnFlushEventArgs($this->_dm)); + } + + // Now we need a commit order to maintain referential integrity + $commitOrder = $this->_getCommitOrder(); + + if ($this->_documentInsertions) { + foreach ($commitOrder as $class) { + $this->_executeInserts($class); + } + foreach ($commitOrder as $class) { + $this->_executeReferenceUpdates($class); + } + } + + if ($this->_documentUpdates) { + foreach ($commitOrder as $class) { + $this->_executeUpdates($class); + } + } + + // Document deletions come last and need to be in reverse commit order + if ($this->_documentDeletions) { + for ($count = count($commitOrder), $i = $count - 1; $i >= 0; --$i) { + $this->_executeDeletions($commitOrder[$i]); + } + } + + // Take new snapshots from visited collections + foreach ($this->_visitedCollections as $coll) { + $coll->takeSnapshot(); + } + + // Clear up + $this->_documentInsertions = + $this->_documentUpdates = + $this->_documentDeletions = + $this->_documentChangeSets = + $this->_visitedCollections = + $this->_scheduledForDirtyCheck = + $this->_orphanRemovals = array(); + } + + /** + * Executes reference updates + * + * @param Doctrine\ODM\MongoDB\Mapping\ClassMetadata $class + */ + private function _executeReferenceUpdates(ClassMetadata $class) + { + $className = $class->name; + $persister = $this->getDocumentPersister($className); + $persister->executeReferenceUpdates(); + } + + /** + * Gets the changeset for an document. + * + * @return array + */ + public function getDocumentChangeSet($document) + { + $oid = spl_object_hash($document); + if (isset($this->_documentChangeSets[$oid])) { + return $this->_documentChangeSets[$oid]; + } + return array(); + } + + /** + * Computes the changes that happened to a single document. + * + * Modifies/populates the following properties: + * + * {@link _originalDocumentData} + * If the document is NEW or MANAGED but not yet fully persisted (only has an id) + * then it was not fetched from the database and therefore we have no original + * document data yet. All of the current document data is stored as the original document data. + * + * {@link _documentChangeSets} + * The changes detected on all properties of the document are stored there. + * A change is a tuple array where the first entry is the old value and the second + * entry is the new value of the property. Changesets are used by persisters + * to INSERT/UPDATE the persistent document state. + * + * {@link _documentUpdates} + * If the document is already fully MANAGED (has been fetched from the database before) + * and any changes to its properties are detected, then a reference to the document is stored + * there to mark it for an update. + * + * @param ClassMetadata $class The class descriptor of the document. + * @param object $document The document for which to compute the changes. + */ + public function computeChangeSet(Mapping\ClassMetadata $class, $document) + { + if ( ! $class->isInheritanceTypeNone()) { + $class = $this->_dm->getClassMetadata(get_class($document)); + } + + $oid = spl_object_hash($document); + + $actualData = array(); + foreach ($class->reflFields as $name => $refProp) { + if ( ! $class->isIdentifier($name) || $class->getAllowCustomID()) { + $actualData[$name] = $refProp->getValue($document); + } + + if ($class->isCollectionValuedReference($name) && $actualData[$name] !== null + && ! ($actualData[$name] instanceof PersistentCollection)) { + // If $actualData[$name] is not a Collection then use an ArrayCollection. + if ( ! $actualData[$name] instanceof Collection) { + $actualData[$name] = new ArrayCollection($actualData[$name]); + } + + $mapping = $class->fieldMappings[$name]; + + // Inject PersistentCollection + $coll = new PersistentCollection( + $this->_dm, + $this->_dm->getClassMetadata($mapping['targetDocument']), + $actualData[$name] + ); + + $coll->setOwner($document, $mapping); + $coll->setDirty( ! $coll->isEmpty()); + $class->reflFields[$name]->setValue($document, $coll); + $actualData[$name] = $coll; + } + } + + if ( ! isset($this->_originalDocumentData[$oid])) { + // Document is either NEW or MANAGED but not yet fully persisted (only has an id). + // These result in an INSERT. + $this->_originalDocumentData[$oid] = $actualData; + $this->_documentChangeSets[$oid] = array_map( + function($e) { return array(null, $e); }, $actualData + ); + } else { + // Document is "fully" MANAGED: it was already fully persisted before + // and we have a copy of the original data + $originalData = $this->_originalDocumentData[$oid]; + $changeSet = array(); + $documentIsDirty = false; + + foreach ($actualData as $propName => $actualValue) { + $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null; + + if (is_object($orgValue)) { + if ($orgValue instanceof PersistentCollection) { + $orgValue = $orgValue->getSnapshot(); + } + if ($actualValue instanceof PersistentCollection) { + $actualValue = $actualValue->toArray(); + } + if ($orgValue !== $actualValue) { + $changeSet[$propName] = array($orgValue, $actualValue); + } + } elseif ($orgValue != $actualValue || ($orgValue === null ^ $actualValue === null)) { + $changeSet[$propName] = array($orgValue, $actualValue); + } + + if (isset($changeSet[$propName])) { + if (isset($class->fieldMappings[$propName]['reference'])) { + $mapping = $class->fieldMappings[$propName]; + if ($mapping['type'] === 'one') { + $documentIsDirty = true; + if ($actualValue === null) { + $this->scheduleOrphanRemoval($orgValue); + } + } + } else { + $documentIsDirty = true; + } + } + } + if ($changeSet) { + $this->_documentChangeSets[$oid] = $changeSet; + $this->_originalDocumentData[$oid] = $actualData; + + if ($documentIsDirty) { + $this->_documentUpdates[$oid] = $document; + } + } + } + + // Look for changes in references of the document + foreach ($class->fieldMappings as $mapping) { + if ( ! isset($mapping['reference'])) { + continue; + } + $val = $class->reflFields[$mapping['fieldName']]->getValue($document); + if ($val !== null) { + $this->_computeAssociationChanges($mapping, $val); + } + } + } + + /** + * Computes all the changes that have been done to documents and collections + * since the last commit and stores these changes in the _documentChangeSet map + * temporarily for access by the persisters, until the UoW commit is finished. + */ + public function computeChangeSets() + { + // Compute changes for INSERTed documents first. This must always happen. + foreach ($this->_documentInsertions as $document) { + $class = $this->_dm->getClassMetadata(get_class($document)); + $this->computeChangeSet($class, $document); + } + + // Compute changes for other MANAGED documents. Change tracking policies take effect here. + foreach ($this->_identityMap as $className => $documents) { + $class = $this->_dm->getClassMetadata($className); + + foreach ($documents as $document) { + // Ignore uninitialized proxy objects + if (/* $document is readOnly || */ $document instanceof Proxy && ! $document->__isInitialized__) { + continue; + } + // Only MANAGED documents that are NOT SCHEDULED FOR INSERTION are processed here. + $oid = spl_object_hash($document); + if ( ! isset($this->_documentInsertions[$oid]) && isset($this->_documentStates[$oid])) { + $this->computeChangeSet($class, $document); + } + } + } + } + + /** + * Computes the changes of an association. + * + * @param AssociationMapping $mapping + * @param mixed $value The value of the association. + */ + private function _computeAssociationChanges($mapping, $value) + { + if ($value instanceof PersistentCollection && $value->isDirty()) { + $this->_visitedCollections[] = $value; + } + + if ( ! $mapping['isCascadePersist']) { + return; // "Persistence by reachability" only if persist cascade specified + } + + // Look through the documents, and in any of their reference, for transient + // enities, recursively. ("Persistence by reachability") + if ($mapping['type'] === 'one') { + if ($value instanceof Proxy && ! $value->__isInitialized__) { + return; // Ignore uninitialized proxy objects + } + $value = array($value); + } elseif ($value instanceof PersistentCollection) { + $value = $value->unwrap(); + } + + $targetClass = $this->_dm->getClassMetadata($mapping['targetDocument']); + foreach ($value as $entry) { + $state = $this->getDocumentState($entry, self::STATE_NEW); + $oid = spl_object_hash($entry); + if ($state == self::STATE_NEW) { + if (isset($targetClass->lifecycleCallbacks[ODMEvents::prePersist])) { + $targetClass->invokeLifecycleCallbacks(ODMEvents::prePersist, $entry); + } + if ($this->_evm->hasListeners(ODMEvents::prePersist)) { + $this->_evm->dispatchEvent(ODMEvents::prePersist, new LifecycleEventArgs($entry, $this->_dm)); + } + + $this->_documentStates[$oid] = self::STATE_MANAGED; + + $this->_documentInsertions[$oid] = $entry; + + $this->computeChangeSet($targetClass, $entry); + + } elseif ($state == self::STATE_REMOVED) { + throw MongoDBException::removedDocumentInCollectionDetected($entry, $mapping); + } + // MANAGED associated documents are already taken into account + // during changeset calculation anyway, since they are in the identity map. + } + } + + /** + * INTERNAL: + * Computes the changeset of an individual document, independently of the + * computeChangeSets() routine that is used at the beginning of a UnitOfWork#commit(). + * + * The passed document must be a managed document. If the document already has a change set + * because this method is invoked during a commit cycle then the change sets are added. + * whereby changes detected in this method prevail. + * + * @ignore + * @param ClassMetadata $class The class descriptor of the document. + * @param object $document The document for which to (re)calculate the change set. + * @throws InvalidArgumentException If the passed document is not MANAGED. + */ + public function recomputeSingleDocumentChangeSet($class, $document) + { + $oid = spl_object_hash($document); + + if ( ! isset($this->_documentStates[$oid]) || $this->_documentStates[$oid] != self::STATE_MANAGED) { + throw new \InvalidArgumentException('Document must be managed.'); + } + + if ( ! $class->isInheritanceTypeNone()) { + $class = $this->_dm->getClassMetadata(get_class($document)); + } + + $actualData = array(); + foreach ($class->reflFields as $name => $refProp) { + if ( ! $class->isIdentifier($name)) { + $actualData[$name] = $refProp->getValue($document); + } + } + + $originalData = $this->_originalDocumentData[$oid]; + $changeSet = array(); + + foreach ($actualData as $propName => $actualValue) { + $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null; + if (is_object($orgValue) && $orgValue !== $actualValue) { + $changeSet[$propName] = array($orgValue, $actualValue); + } elseif ($orgValue != $actualValue || ($orgValue === null ^ $actualValue === null)) { + $changeSet[$propName] = array($orgValue, $actualValue); + } + } + + if ($changeSet) { + if (isset($this->_documentChangeSets[$oid])) { + $this->_documentChangeSets[$oid] = $changeSet + $this->_documentChangeSets[$oid]; + } + $this->_originalDocumentData[$oid] = $actualData; + } + } + + /** + * Executes all document insertions for documents of the specified type. + * + * @param Doctrine\ODM\MongoDB\Mapping\ClassMetadata $class + */ + private function _executeInserts($class) + { + $className = $class->name; + $persister = $this->getDocumentPersister($className); + $collection = $this->_dm->getDocumentCollection($className); + + $hasLifecycleCallbacks = isset($class->lifecycleCallbacks[ODMEvents::postPersist]); + $hasListeners = $this->_evm->hasListeners(ODMEvents::postPersist); + if ($hasLifecycleCallbacks || $hasListeners) { + $documents = array(); + } + + $inserts = array(); + foreach ($this->_documentInsertions as $oid => $document) { + if (get_class($document) === $className) { + $persister->addInsert($document); + unset($this->_documentInsertions[$oid]); + if ($hasLifecycleCallbacks || $hasListeners) { + $documents[] = $document; + } + } + } + + $postInsertIds = $persister->executeInserts(); + + if ($postInsertIds) { + foreach ($postInsertIds as $pair) { + list($id, $document) = $pair; + $oid = spl_object_hash($document); + $class->setIdentifierValue($document, $id); + $this->_documentIdentifiers[$oid] = $id; + $this->_documentStates[$oid] = self::STATE_MANAGED; + $this->_originalDocumentData[$oid][$class->identifier] = $id; + $this->addToIdentityMap($document); + } + } + + if ($hasLifecycleCallbacks || $hasListeners) { + foreach ($documents as $document) { + if ($hasLifecycleCallbacks) { + $class->invokeLifecycleCallbacks(ODMEvents::postPersist, $document); + } + if ($hasListeners) { + $this->_evm->dispatchEvent(ODMEvents::postPersist, new LifecycleEventArgs($document, $this->_dm)); + } + } + } + } + + /** + * Executes all document updates for documents of the specified type. + * + * @param Doctrine\ODM\MongoDB\Mapping\ClassMetadata $class + */ + private function _executeUpdates($class) + { + $className = $class->name; + $persister = $this->getDocumentPersister($className); + + $hasPreUpdateLifecycleCallbacks = isset($class->lifecycleCallbacks[ODMEvents::preUpdate]); + $hasPreUpdateListeners = $this->_evm->hasListeners(ODMEvents::preUpdate); + $hasPostUpdateLifecycleCallbacks = isset($class->lifecycleCallbacks[ODMEvents::postUpdate]); + $hasPostUpdateListeners = $this->_evm->hasListeners(ODMEvents::postUpdate); + + foreach ($this->_documentUpdates as $oid => $document) { + if (get_class($document) == $className || $document instanceof Proxy && $document instanceof $className) { + if ($hasPreUpdateLifecycleCallbacks) { + $class->invokeLifecycleCallbacks(ODMEvents::preUpdate, $document); + $this->recomputeSingleDocumentChangeSet($class, $document); + } + + if ($hasPreUpdateListeners) { + $this->_evm->dispatchEvent(ODMEvents::preUpdate, new Event\PreUpdateEventArgs( + $document, $this->_dm, $this->_documentChangeSets[$oid]) + ); + } + + $persister->update($document); + unset($this->_documentUpdates[$oid]); + + if ($hasPostUpdateLifecycleCallbacks) { + $class->invokeLifecycleCallbacks(ODMEvents::postUpdate, $document); + } + if ($hasPostUpdateListeners) { + $this->_evm->dispatchEvent(ODMEvents::postUpdate, new LifecycleEventArgs($document, $this->_dm)); + } + } + } + } + /** + * Executes all document deletions for documents of the specified type. + * + * @param Doctrine\ODM\MongoDB\Mapping\ClassMetadata $class + */ + private function _executeDeletions($class) + { + $hasLifecycleCallbacks = isset($class->lifecycleCallbacks[ODMEvents::postRemove]); + $hasListeners = $this->_evm->hasListeners(ODMEvents::postRemove); + + $className = $class->name; + $persister = $this->getDocumentPersister($className); + $collection = $this->_dm->getDocumentCollection($className); + foreach ($this->_documentDeletions as $oid => $document) { + if (get_class($document) == $className || $document instanceof Proxy && $document instanceof $className) { + $persister->delete($document); + unset( + $this->_documentDeletions[$oid], + $this->_documentIdentifiers[$oid], + $this->_originalDocumentData[$oid] + ); + // Document with this $oid after deletion treated as NEW, even if the $oid + // is obtained by a new document because the old one went out of scope. + $this->_documentStates[$oid] = self::STATE_NEW; + + if ($hasLifecycleCallbacks) { + $class->invokeLifecycleCallbacks(ODMEvents::postRemove, $document); + } + if ($hasListeners) { + $this->_evm->dispatchEvent(ODMEvents::postRemove, new LifecycleEventArgs($document, $this->_dm)); + } + } + } + } + + /** + * Gets the commit order. + * + * @return array + */ + private function _getCommitOrder(array $documentChangeSet = null) + { + if ($documentChangeSet === null) { + $documentChangeSet = array_merge( + $this->_documentInsertions, + $this->_documentUpdates, + $this->_documentDeletions + ); + } + + $calc = $this->getCommitOrderCalculator(); + + // See if there are any new classes in the changeset, that are not in the + // commit order graph yet (dont have a node). + $newNodes = array(); + foreach ($documentChangeSet as $oid => $document) { + $className = get_class($document); + if ( ! $calc->hasClass($className)) { + $class = $this->_dm->getClassMetadata($className); + $calc->addClass($class); + $newNodes[] = $class; + } + } + + // Calculate dependencies for new nodes + foreach ($newNodes as $class) { + $this->_addDependencies($class, $calc); + } + + $classes = $calc->getCommitOrder(); + foreach ($classes as $key => $class) { + if ($class->isEmbeddedDocument) { + unset($classes[$key]); + } + } + return array_values($classes); + } + + /** + * Add dependencies recursively through embedded documents. Embedded documents + * may have references to other documents so those need to be saved first. + * + * @param ClassMetadata $class + * @param CommitOrderCalculator $calc + */ + private function _addDependencies($class, $calc) + { + foreach ($class->fieldMappings as $mapping) { + if (isset($mapping['reference'])) { + $targetClass = $this->_dm->getClassMetadata($mapping['targetDocument']); + if ( ! $calc->hasClass($targetClass->name)) { + $calc->addClass($targetClass); + } + if ( ! $calc->hasDependency($targetClass, $class)) { + $calc->addDependency($targetClass, $class); + } + } + if (isset($mapping['embedded'])) { + $targetClass = $this->_dm->getClassMetadata($mapping['targetDocument']); + if ( ! $calc->hasClass($targetClass->name)) { + $calc->addClass($targetClass); + } + if ( ! $calc->hasDependency($targetClass, $class)) { + $calc->addDependency($targetClass, $class); + } + + $this->_addDependencies($targetClass, $calc); + } + } + } + + /** + * Schedules an document for insertion into the database. + * If the document already has an identifier, it will be added to the identity map. + * + * @param object $document The document to schedule for insertion. + */ + public function scheduleForInsert($document) + { + $oid = spl_object_hash($document); + + if (isset($this->_documentUpdates[$oid])) { + throw new \InvalidArgumentException("Dirty document can not be scheduled for insertion."); + } + if (isset($this->_documentDeletions[$oid])) { + throw new \InvalidArgumentException("Removed document can not be scheduled for insertion."); + } + if (isset($this->_documentInsertions[$oid])) { + throw new \InvalidArgumentException("Document can not be scheduled for insertion twice."); + } + + $this->_documentInsertions[$oid] = $document; + + if (isset($this->_documentIdentifiers[$oid])) { + $this->addToIdentityMap($document); + } + } + + /** + * Checks whether an document is scheduled for insertion. + * + * @param object $document + * @return boolean + */ + public function isScheduledForInsert($document) + { + return isset($this->_documentInsertions[spl_object_hash($document)]); + } + + /** + * Schedules an document for being updated. + * + * @param object $document The document to schedule for being updated. + */ + public function scheduleForUpdate($document) + { + $oid = spl_object_hash($document); + if ( ! isset($this->_documentIdentifiers[$oid])) { + throw new \InvalidArgumentException("Document has no identity."); + } + if (isset($this->_documentDeletions[$oid])) { + throw new \InvalidArgumentException("Document is removed."); + } + + if ( ! isset($this->_documentUpdates[$oid]) && ! isset($this->_documentInsertions[$oid])) { + $this->_documentUpdates[$oid] = $document; + } + } + + /** + * Checks whether an document is registered as dirty in the unit of work. + * Note: Is not very useful currently as dirty documents are only registered + * at commit time. + * + * @param object $document + * @return boolean + */ + public function isScheduledForUpdate($document) + { + return isset($this->_documentUpdates[spl_object_hash($document)]); + } + + /** + * INTERNAL: + * Schedules an document for deletion. + * + * @param object $document + */ + public function scheduleForDelete($document) + { + $oid = spl_object_hash($document); + + if (isset($this->_documentInsertions[$oid])) { + if ($this->isInIdentityMap($document)) { + $this->removeFromIdentityMap($document); + } + unset($this->_documentInsertions[$oid]); + return; // document has not been persisted yet, so nothing more to do. + } + + if ( ! $this->isInIdentityMap($document)) { + return; // ignore + } + + $this->removeFromIdentityMap($document); + + if (isset($this->_documentUpdates[$oid])) { + unset($this->_documentUpdates[$oid]); + } + if ( ! isset($this->_documentDeletions[$oid])) { + $this->_documentDeletions[$oid] = $document; + } + } + + /** + * Checks whether an document is registered as removed/deleted with the unit + * of work. + * + * @param object $document + * @return boolean + */ + public function isScheduledForDelete($document) + { + return isset($this->_documentDeletions[spl_object_hash($document)]); + } + + /** + * Checks whether an document is scheduled for insertion, update or deletion. + * + * @param $document + * @return boolean + */ + public function isDocumentScheduled($document) + { + $oid = spl_object_hash($document); + return isset($this->_documentInsertions[$oid]) || + isset($this->_documentUpdates[$oid]) || + isset($this->_documentDeletions[$oid]); + } + + /** + * INTERNAL: + * Registers an document in the identity map. + * Note that documents in a hierarchy are registered with the class name of + * the root document. + * + * @ignore + * @param object $document The document to register. + * @return boolean TRUE if the registration was successful, FALSE if the identity of + * the document in question is already managed. + */ + public function addToIdentityMap($document) + { + $classMetadata = $this->_dm->getClassMetadata(get_class($document)); + $id = $this->_documentIdentifiers[spl_object_hash($document)]; + $id = $classMetadata->getPHPIdentifierValue($id); + if ($id === '') { + throw new \InvalidArgumentException("The given document has no identity."); + } + $className = $classMetadata->rootDocumentName; + if (isset($this->_identityMap[$className][$id])) { + return false; + } + $this->_identityMap[$className][$id] = $document; + return true; + } + + /** + * Gets the state of an document within the current unit of work. + * + * NOTE: This method sees documents that are not MANAGED or REMOVED and have a + * populated identifier, whether it is generated or manually assigned, as + * DETACHED. This can be incorrect for manually assigned identifiers. + * + * @param object $document + * @param integer $assume The state to assume if the state is not yet known. This is usually + * used to avoid costly state lookups, in the worst case with a database + * lookup. + * @return int The document state. + */ + public function getDocumentState($document, $assume = null) + { + $oid = spl_object_hash($document); + if ( ! isset($this->_documentStates[$oid])) { + // State can only be NEW or DETACHED, because MANAGED/REMOVED states are immediately + // set by the UnitOfWork directly. We treat all documents that have a populated + // identifier as DETACHED and all others as NEW. This is not really correct for + // manually assigned identifiers but in that case we would need to hit the database + // and we would like to avoid that. + if ($assume === null) { + if ($this->_dm->getClassMetadata(get_class($document))->getIdentifierValue($document)) { + $this->_documentStates[$oid] = self::STATE_DETACHED; + } else { + $this->_documentStates[$oid] = self::STATE_NEW; + } + } else { + $this->_documentStates[$oid] = $assume; + } + } + return $this->_documentStates[$oid]; + } + + /** + * INTERNAL: + * Removes an document from the identity map. This effectively detaches the + * document from the persistence management of Doctrine. + * + * @ignore + * @param object $document + * @return boolean + */ + public function removeFromIdentityMap($document) + { + $oid = spl_object_hash($document); + $classMetadata = $this->_dm->getClassMetadata(get_class($document)); + $id = $this->_documentIdentifiers[$oid]; + $id = $classMetadata->getPHPIdentifierValue($id); + if ($id === '') { + throw new \InvalidArgumentException("The given document has no identity."); + } + $className = $classMetadata->rootDocumentName; + if (isset($this->_identityMap[$className][$id])) { + unset($this->_identityMap[$className][$id]); + $this->_documentStates[$oid] = self::STATE_DETACHED; + return true; + } + + return false; + } + + /** + * INTERNAL: + * Gets an document in the identity map by its identifier hash. + * + * @ignore + * @param string $id + * @param string $rootClassName + * @return object + */ + public function getById($id, $rootClassName) + { + return $this->_identityMap[$rootClassName][$id]; + } + + /** + * INTERNAL: + * Tries to get an document by its identifier hash. If no document is found for + * the given hash, FALSE is returned. + * + * @ignore + * @param string $id + * @param string $rootClassName + * @return mixed The found document or FALSE. + */ + public function tryGetById($id, $rootClassName) + { + return isset($this->_identityMap[$rootClassName][$id]) ? + $this->_identityMap[$rootClassName][$id] : false; + } + + /** + * Checks whether an document is registered in the identity map of this UnitOfWork. + * + * @param object $document + * @return boolean + */ + public function isInIdentityMap($document) + { + $oid = spl_object_hash($document); + if ( ! isset($this->_documentIdentifiers[$oid])) { + return false; + } + $classMetadata = $this->_dm->getClassMetadata(get_class($document)); + $id = $this->_documentIdentifiers[$oid]; + $id = $classMetadata->getPHPIdentifierValue($id); + if ($id === '') { + return false; + } + + return isset($this->_identityMap[$classMetadata->rootDocumentName][$id]); + } + + /** + * INTERNAL: + * Checks whether an identifier hash exists in the identity map. + * + * @ignore + * @param string $id + * @param string $rootClassName + * @return boolean + */ + public function containsId($id, $rootClassName) + { + return isset($this->_identityMap[$rootClassName][$id]); + } + + /** + * Persists an document as part of the current unit of work. + * + * @param object $document The document to persist. + */ + public function persist($document) + { + $visited = array(); + $this->_doPersist($document, $visited); + } + + /** + * Saves an document as part of the current unit of work. + * This method is internally called during save() cascades as it tracks + * the already visited documents to prevent infinite recursions. + * + * NOTE: This method always considers documents that are not yet known to + * this UnitOfWork as NEW. + * + * @param object $document The document to persist. + * @param array $visited The already visited documents. + */ + private function _doPersist($document, array &$visited) + { + $oid = spl_object_hash($document); + if (isset($visited[$oid])) { + return; // Prevent infinite recursion + } + + $visited[$oid] = $document; // Mark visited + + $class = $this->_dm->getClassMetadata(get_class($document)); + $documentState = $this->getDocumentState($document, self::STATE_NEW); + + switch ($documentState) { + case self::STATE_MANAGED: + $this->scheduleForDirtyCheck($document); + break; + case self::STATE_NEW: + if (isset($class->lifecycleCallbacks[ODMEvents::prePersist])) { + $class->invokeLifecycleCallbacks(ODMEvents::prePersist, $document); + } + if ($this->_evm->hasListeners(ODMEvents::prePersist)) { + $this->_evm->dispatchEvent(ODMEvents::prePersist, new LifecycleEventArgs($document, $this->_dm)); + } + + $this->_documentStates[$oid] = self::STATE_MANAGED; + + $this->scheduleForInsert($document); + break; + case self::STATE_DETACHED: + throw new \InvalidArgumentException( + "Behavior of persist() for a detached document is not yet defined."); + case self::STATE_REMOVED: + // Document becomes managed again + if ($this->isScheduledForDelete($document)) { + unset($this->_documentDeletions[$oid]); + } else { + //FIXME: There's more to think of here... + $this->scheduleForInsert($document); + } + break; + default: + throw MongoDBException::invalidDocumentState($documentState); + } + + $this->_cascadePersist($document, $visited); + } + + /** + * Deletes an document as part of the current unit of work. + * + * @param object $document The document to remove. + */ + public function remove($document) + { + $visited = array(); + $this->_doRemove($document, $visited); + } + + /** + * Deletes an document as part of the current unit of work. + * + * This method is internally called during delete() cascades as it tracks + * the already visited documents to prevent infinite recursions. + * + * @param object $document The document to delete. + * @param array $visited The map of the already visited documents. + * @throws InvalidArgumentException If the instance is a detached document. + */ + private function _doRemove($document, array &$visited) + { + $oid = spl_object_hash($document); + if (isset($visited[$oid])) { + return; // Prevent infinite recursion + } + + $visited[$oid] = $document; // mark visited + + $class = $this->_dm->getClassMetadata(get_class($document)); + $documentState = $this->getDocumentState($document); + switch ($documentState) { + case self::STATE_NEW: + case self::STATE_REMOVED: + // nothing to do + break; + case self::STATE_MANAGED: + if (isset($class->lifecycleCallbacks[ODMEvents::preRemove])) { + $class->invokeLifecycleCallbacks(ODMEvents::preRemove, $document); + } + if ($this->_evm->hasListeners(ODMEvents::preRemove)) { + $this->_evm->dispatchEvent(ODMEvents::preRemove, new LifecycleEventArgs($document, $this->_dm)); + } + $this->scheduleForDelete($document); + break; + case self::STATE_DETACHED: + throw MongoDBException::detachedDocumentCannotBeRemoved(); + default: + throw MongoDBException::invalidDocumentState($documentState); + } + + $this->_cascadeRemove($document, $visited); + } + + /** + * Merges the state of the given detached document into this UnitOfWork. + * + * @param object $document + * @return object The managed copy of the document. + */ + public function merge($document) + { + $visited = array(); + return $this->_doMerge($document, $visited); + } + + /** + * Executes a merge operation on an document. + * + * @param object $document + * @param array $visited + * @return object The managed copy of the document. + * @throws InvalidArgumentException If the document instance is NEW. + */ + private function _doMerge($document, array &$visited, $prevManagedCopy = null, $mapping = null) + { + $class = $this->_dm->getClassMetadata(get_class($document)); + $id = $class->getIdentifierValue($document); + + if ( ! $id) { + throw new \InvalidArgumentException('New document detected during merge.' + . ' Persist the new document before merging.'); + } + + // MANAGED documents are ignored by the merge operation + if ($this->getDocumentState($document, self::STATE_DETACHED) == self::STATE_MANAGED) { + $managedCopy = $document; + } else { + // Try to look the document up in the identity map. + $managedCopy = $this->tryGetById($id, $class->rootDocumentName); + if ($managedCopy) { + // We have the document in-memory already, just make sure its not removed. + if ($this->getDocumentState($managedCopy) == self::STATE_REMOVED) { + throw new \InvalidArgumentException('Removed document detected during merge.' + . ' Can not merge with a removed document.'); + } + } else { + // We need to fetch the managed copy in order to merge. + $managedCopy = $this->_dm->find($class->name, $id); + } + + if ($managedCopy === null) { + throw new \InvalidArgumentException('New document detected during merge.' + . ' Persist the new document before merging.'); + } + + // Merge state of $document into existing (managed) document + foreach ($class->reflFields as $name => $prop) { + if ( ! isset($class->fieldMappings[$name]['reference'])) { + $prop->setValue($managedCopy, $prop->getValue($document)); + } else { + $mapping2 = $class->fieldMappings[$name]; + if ($mapping2['type'] === 'one') { + if ( ! $assoc2['isCascadeMerge']) { + $other = $class->reflFields[$name]->getValue($document); //TODO: Just $prop->getValue($document)? + if ($other !== null) { + $targetClass = $this->_dm->getClassMetadata($mapping2['targetDocument']); + $id = $targetClass->getIdentifierValue($other); + $proxy = $this->_dm->getProxyFactory()->getProxy($mapping2['targetDocument'], $id); + $prop->setValue($managedCopy, $proxy); + $this->registerManaged($proxy, $id, array()); + } + } + } else { + $coll = new PersistentCollection($this->_dm, + $this->_dm->getClassMetadata($mapping2['targetDocument']), + new ArrayCollection + ); + $coll->setOwner($managedCopy, $mapping2); + $coll->setInitialized($mapping2['isCascadeMerge']); + $prop->setValue($managedCopy, $coll); + } + } + } + } + + if ($prevManagedCopy !== null) { + $assocField = $mapping['fieldName']; + $prevClass = $this->_dm->getClassMetadata(get_class($prevManagedCopy)); + if ($mapping['type'] === 'one') { + $prevClass->reflFields[$assocField]->setValue($prevManagedCopy, $managedCopy); + } else { + $prevClass->reflFields[$assocField]->getValue($prevManagedCopy)->unwrap()->add($managedCopy); + } + } + + $this->_cascadeMerge($document, $managedCopy, $visited); + + return $managedCopy; + } + + /** + * Detaches an document from the persistence management. It's persistence will + * no longer be managed by Doctrine. + * + * @param object $document The document to detach. + */ + public function detach($document) + { + $visited = array(); + $this->_doDetach($document, $visited); + } + + /** + * Executes a detach operation on the given document. + * + * @param object $document + * @param array $visited + * @internal This method always considers documents with an assigned identifier as DETACHED. + */ + private function _doDetach($document, array &$visited) + { + $oid = spl_object_hash($document); + if (isset($visited[$oid])) { + return; // Prevent infinite recursion + } + + $visited[$oid] = $document; // mark visited + + switch ($this->getDocumentState($document, self::STATE_DETACHED)) { + case self::STATE_MANAGED: + $this->removeFromIdentityMap($document); + unset($this->_documentInsertions[$oid], $this->_documentUpdates[$oid], + $this->_documentDeletions[$oid], $this->_documentIdentifiers[$oid], + $this->_documentStates[$oid], $this->_originalDocumentData[$oid]); + break; + case self::STATE_NEW: + case self::STATE_DETACHED: + return; + } + + $this->_cascadeDetach($document, $visited); + } + + /** + * Refreshes the state of the given document from the database, overwriting + * any local, unpersisted changes. + * + * @param object $document The document to refresh. + * @throws InvalidArgumentException If the document is not MANAGED. + */ + public function refresh($document) + { + $visited = array(); + $this->_doRefresh($document, $visited); + } + + /** + * Executes a refresh operation on an document. + * + * @param object $document The document to refresh. + * @param array $visited The already visited documents during cascades. + * @throws InvalidArgumentException If the document is not MANAGED. + */ + private function _doRefresh($document, array &$visited) + { + $oid = spl_object_hash($document); + if (isset($visited[$oid])) { + return; // Prevent infinite recursion + } + + $visited[$oid] = $document; // mark visited + + $class = $this->_dm->getClassMetadata(get_class($document)); + if ($this->getDocumentState($document) == self::STATE_MANAGED) { + $this->getDocumentPersister($class->name)->refresh($document); + } else { + throw new \InvalidArgumentException("Document is not MANAGED."); + } + + $this->_cascadeRefresh($document, $visited); + } + + /** + * Cascades a refresh operation to associated documents. + * + * @param object $document + * @param array $visited + */ + private function _cascadeRefresh($document, array &$visited) + { + $class = $this->_dm->getClassMetadata(get_class($document)); + foreach ($class->fieldMappings as $mapping) { + if ( ! isset($mapping['reference']) || ! $mapping['isCascadeRefresh']) { + continue; + } + if (isset($mapping['embedded'])) { + $relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document); + if (($relatedDocuments instanceof Collection || is_array($relatedDocuments))) { + if ($relatedDocuments instanceof PersistentCollection) { + // Unwrap so that foreach() does not initialize + $relatedDocuments = $relatedDocuments->unwrap(); + } + foreach ($relatedDocuments as $relatedDocument) { + $this->_cascadeRefresh($relatedDocument, $visited); + } + } elseif ($relatedDocuments !== null) { + $this->_cascadeRefresh($relatedDocuments, $visited); + } + } elseif (isset($mapping['reference'])) { + $relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document); + if (($relatedDocuments instanceof Collection || is_array($relatedDocuments))) { + if ($relatedDocuments instanceof PersistentCollection) { + // Unwrap so that foreach() does not initialize + $relatedDocuments = $relatedDocuments->unwrap(); + } + foreach ($relatedDocuments as $relatedDocument) { + $this->_doRefresh($relatedDocument, $visited); + } + } elseif ($relatedDocuments !== null) { + $this->_doRefresh($relatedDocuments, $visited); + } + } + } + } + + /** + * Cascades a detach operation to associated documents. + * + * @param object $document + * @param array $visited + */ + private function _cascadeDetach($document, array &$visited) + { + $class = $this->_dm->getClassMetadata(get_class($document)); + foreach ($class->fieldMappings as $mapping) { + if ( ! isset($mapping['embedded']) && (!isset($mapping['reference']) || ! $mapping['isCascadeDetach'])) { + continue; + } + if (isset($mapping['embedded'])) { + $relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document); + if (($relatedDocuments instanceof Collection || is_array($relatedDocuments))) { + if ($relatedDocuments instanceof PersistentCollection) { + // Unwrap so that foreach() does not initialize + $relatedDocuments = $relatedDocuments->unwrap(); + } + foreach ($relatedDocuments as $relatedDocument) { + $this->_cascadeDetach($relatedDocument, $visited); + } + } elseif ($relatedDocuments !== null) { + $this->_cascadeDetach($relatedDocuments, $visited); + } + } elseif (isset($mapping['reference'])) { + $relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document); + if (($relatedDocuments instanceof Collection || is_array($relatedDocuments))) { + if ($relatedDocuments instanceof PersistentCollection) { + // Unwrap so that foreach() does not initialize + $relatedDocuments = $relatedDocuments->unwrap(); + } + foreach ($relatedDocuments as $relatedDocument) { + $this->_doDetach($relatedDocument, $visited); + } + } elseif ($relatedDocuments !== null) { + $this->_doDetach($relatedDocuments, $visited); + } + } + } + } + + /** + * Cascades a merge operation to associated documents. + * + * @param object $document + * @param object $managedCopy + * @param array $visited + */ + private function _cascadeMerge($document, $managedCopy, array &$visited) + { + $class = $this->_dm->getClassMetadata(get_class($document)); + foreach ($class->fieldMappings as $mapping) { + if ( ! isset($mapping['embedded']) && (!isset($mapping['reference']) || ! $mapping['isCascadeMerge'])) { + continue; + } + if (isset($mapping['embedded'])) { + $relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document); + if (($relatedDocuments instanceof Collection || is_array($relatedDocuments))) { + if ($relatedDocuments instanceof PersistentCollection) { + // Unwrap so that foreach() does not initialize + $relatedDocuments = $relatedDocuments->unwrap(); + } + foreach ($relatedDocuments as $relatedDocument) { + $this->_cascadeMerge($relatedDocument, $managedCopy, $visited); + } + } elseif ($relatedDocuments !== null) { + $this->_cascadeMerge($relatedDocuments, $managedCopy, $visited); + } + } elseif (isset($mapping['reference'])) { + $relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document); + if (($relatedDocuments instanceof Collection || is_array($relatedDocuments))) { + if ($relatedDocuments instanceof PersistentCollection) { + // Unwrap so that foreach() does not initialize + $relatedDocuments = $relatedDocuments->unwrap(); + } + foreach ($relatedDocuments as $relatedDocument) { + $this->_doMerge($relatedDocument, $visited); + } + } elseif ($relatedDocuments !== null) { + $this->_doMerge($relatedDocuments, $visited); + } + } + } + } + + /** + * Cascades the save operation to associated documents. + * + * @param object $document + * @param array $visited + * @param array $insertNow + */ + private function _cascadePersist($document, array &$visited) + { + $class = $this->_dm->getClassMetadata(get_class($document)); + foreach ($class->fieldMappings as $mapping) { + if ( ! isset($mapping['embedded']) && (!isset($mapping['reference']) || !$mapping['isCascadePersist'])) { + continue; + } + if (isset($mapping['embedded'])) { + $relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document); + if (($relatedDocuments instanceof Collection || is_array($relatedDocuments))) { + if ($relatedDocuments instanceof PersistentCollection) { + // Unwrap so that foreach() does not initialize + $relatedDocuments = $relatedDocuments->unwrap(); + } + foreach ($relatedDocuments as $relatedDocument) { + $this->_cascadePersist($relatedDocument, $visited); + } + } elseif ($relatedDocuments !== null) { + $this->_cascadePersist($relatedDocuments, $visited); + } + } elseif (isset($mapping['reference'])) { + $relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document); + if (($relatedDocuments instanceof Collection || is_array($relatedDocuments))) { + if ($relatedDocuments instanceof PersistentCollection) { + // Unwrap so that foreach() does not initialize + $relatedDocuments = $relatedDocuments->unwrap(); + } + foreach ($relatedDocuments as $relatedDocument) { + $this->_doPersist($relatedDocument, $visited); + } + } elseif ($relatedDocuments !== null) { + $this->_doPersist($relatedDocuments, $visited); + } + } + } + } + + /** + * Cascades the delete operation to associated documents. + * + * @param object $document + * @param array $visited + */ + private function _cascadeRemove($document, array &$visited) + { + $class = $this->_dm->getClassMetadata(get_class($document)); + foreach ($class->fieldMappings as $mapping) { + if ( ! isset($mapping['embedded']) && (!isset($mapping['reference']) || ! $mapping['isCascadeRemove'])) { + continue; + } + if (isset($mapping['embedded'])) { + $relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document); + if (($relatedDocuments instanceof Collection || is_array($relatedDocuments))) { + if ($relatedDocuments instanceof PersistentCollection) { + // Unwrap so that foreach() does not initialize + $relatedDocuments = $relatedDocuments->unwrap(); + } + foreach ($relatedDocuments as $relatedDocument) { + $this->_cascadeRemove($relatedDocument, $visited); + } + } elseif ($relatedDocuments !== null) { + $this->_cascadeRemove($relatedDocuments, $visited); + } + } elseif (isset($mapping['reference'])) { + $relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document); + if (($relatedDocuments instanceof Collection || is_array($relatedDocuments))) { + if ($relatedDocuments instanceof PersistentCollection) { + // Unwrap so that foreach() does not initialize + $relatedDocuments = $relatedDocuments->unwrap(); + } + foreach ($relatedDocuments as $relatedDocument) { + $this->_doRemove($relatedDocument, $visited); + } + } elseif ($relatedDocuments !== null) { + $this->_doRemove($relatedDocuments, $visited); + } + } + } + } + + /** + * Gets the CommitOrderCalculator used by the UnitOfWork to order commits. + * + * @return Doctrine\ODM\MongoDB\Internal\CommitOrderCalculator + */ + public function getCommitOrderCalculator() + { + if ($this->_commitOrderCalculator === null) { + $this->_commitOrderCalculator = new Internal\CommitOrderCalculator; + } + return $this->_commitOrderCalculator; + } + + /** + * Clears the UnitOfWork. + */ + public function clear() + { + $this->_identityMap = + $this->_documentIdentifiers = + $this->_originalDocumentData = + $this->_documentChangeSets = + $this->_documentStates = + $this->_scheduledForDirtyCheck = + $this->_documentInsertions = + $this->_documentUpdates = + $this->_documentDeletions = + $this->_orphanRemovals = array(); + if ($this->_commitOrderCalculator !== null) { + $this->_commitOrderCalculator->clear(); + } + } + + /** + * INTERNAL: + * Schedules an orphaned document for removal. The remove() operation will be + * invoked on that document at the beginning of the next commit of this + * UnitOfWork. + * + * @ignore + * @param object $document + */ + public function scheduleOrphanRemoval($document) + { + $this->_orphanRemovals[spl_object_hash($document)] = $document; + } + + public function isCollectionScheduledForDeletion(PersistentCollection $coll) + { + return in_array($coll, $this->_collectionsDeletions, true); + } + + /** + * INTERNAL: + * Creates an document. Used for reconstitution of documents during hydration. + * + * @ignore + * @param string $className The name of the document class. + * @param array $data The data for the document. + * @param array $hints Any hints to account for during reconstitution/lookup of the document. + * @return object The document instance. + * @internal Highly performance-sensitive method. + */ + public function getOrCreateDocument($className, array $data, &$hints = array()) + { + $class = $this->_dm->getClassMetadata($className); + + $id = $class->getPHPIdentifierValue($data['_id']); + if (isset($this->_identityMap[$class->rootDocumentName][$id])) { + $document = $this->_identityMap[$class->rootDocumentName][$id]; + $oid = spl_object_hash($document); + if ($document instanceof Proxy && ! $document->__isInitialized__) { + $document->__isInitialized__ = true; + $overrideLocalValues = true; + } else { + $overrideLocalValues = isset($hints[Query::HINT_REFRESH]); + } + } else { + $document = $class->newInstance(); + $oid = spl_object_hash($document); + $this->_documentIdentifiers[$oid] = $id; + $this->_documentStates[$oid] = self::STATE_MANAGED; + $this->_originalDocumentData[$oid] = $data; + $this->_identityMap[$class->rootDocumentName][$id] = $document; + $overrideLocalValues = true; + } + if ($overrideLocalValues) { + $this->_hydrator->hydrate($class, $document, $data); + } + if (isset($class->lifecycleCallbacks[ODMEvents::postLoad])) { + $class->invokeLifecycleCallbacks(ODMEvents::postLoad, $document); + } + if ($this->_evm->hasListeners(ODMEvents::postLoad)) { + $this->_evm->dispatchEvent(ODMEvents::postLoad, new LifecycleEventArgs($document, $this->_dm)); + } + return $document; + } + + /** + * Gets the identity map of the UnitOfWork. + * + * @return array + */ + public function getIdentityMap() + { + return $this->_identityMap; + } + + /** + * Gets the original data of an document. The original data is the data that was + * present at the time the document was reconstituted from the database. + * + * @param object $document + * @return array + */ + public function getOriginalDocumentData($document) + { + $oid = spl_object_hash($document); + if (isset($this->_originalDocumentData[$oid])) { + return $this->_originalDocumentData[$oid]; + } + return array(); + } + + /** + * @ignore + */ + public function setOriginalDocumentData($document, array $data) + { + $this->_originalDocumentData[spl_object_hash($document)] = $data; + } + + /** + * INTERNAL: + * Sets a property value of the original data array of an document. + * + * @ignore + * @param string $oid + * @param string $property + * @param mixed $value + */ + public function setOriginalDocumentProperty($oid, $property, $value) + { + $this->_originalDocumentData[$oid][$property] = $value; + } + + /** + * Gets the identifier of an document. + * The returned value is always an array of identifier values. If the document + * has a composite identifier then the identifier values are in the same + * order as the identifier field names as returned by ClassMetadata#getIdentifierFieldNames(). + * + * @param object $document + * @return array The identifier values. + */ + public function getDocumentIdentifier($document) + { + return isset($this->_documentIdentifiers[spl_object_hash($document)]) ? + $this->_documentIdentifiers[spl_object_hash($document)] : null; + } + + /** + * Schedules an document for dirty-checking at commit-time. + * + * @param object $document The document to schedule for dirty-checking. + */ + public function scheduleForDirtyCheck($document) + { + $rootClassName = $this->_dm->getClassMetadata(get_class($document))->rootDocumentName; + $this->_scheduledForDirtyCheck[$rootClassName][] = $document; + } + + /** + * Checks whether the UnitOfWork has any pending insertions. + * + * @return boolean TRUE if this UnitOfWork has pending insertions, FALSE otherwise. + */ + public function hasPendingInsertions() + { + return ! empty($this->_documentInsertions); + } + + /** + * Calculates the size of the UnitOfWork. The size of the UnitOfWork is the + * number of documents in the identity map. + * + * @return integer + */ + public function size() + { + $count = 0; + foreach ($this->_identityMap as $documentSet) { + $count += count($documentSet); + } + return $count; + } + + /** + * INTERNAL: + * Registers an document as managed. + * + * @param object $document The document. + * @param array $id The identifier values. + * @param array $data The original document data. + */ + public function registerManaged($document, $id, array $data) + { + $oid = spl_object_hash($document); + $this->_documentIdentifiers[$oid] = $id; + $this->_documentStates[$oid] = self::STATE_MANAGED; + $this->_originalDocumentData[$oid] = $data; + $this->addToIdentityMap($document); + } + + /** + * INTERNAL: + * Clears the property changeset of the document with the given OID. + * + * @param string $oid The document's OID. + */ + public function clearDocumentChangeSet($oid) + { + unset($this->_documentChangeSets[$oid]); + } + + /* PropertyChangedListener implementation */ + + /** + * Notifies this UnitOfWork of a property change in an document. + * + * @param object $document The document that owns the property. + * @param string $propertyName The name of the property that changed. + * @param mixed $oldValue The old value of the property. + * @param mixed $newValue The new value of the property. + */ + public function propertyChanged($document, $propertyName, $oldValue, $newValue) + { + $oid = spl_object_hash($document); + $class = $this->_dm->getClassMetadata(get_class($document)); + + if ( ! isset($class->fieldMappings[$propertyName])) { + return; // ignore non-persistent fields + } + + $this->_documentChangeSets[$oid][$propertyName] = array($oldValue, $newValue); + + $this->_documentUpdates[$oid] = $document; + } + + /** + * Gets the currently scheduled document insertions in this UnitOfWork. + * + * @return array + */ + public function getScheduledDocumentInsertions() + { + return $this->_documentInsertions; + } + + /** + * Gets the currently scheduled document updates in this UnitOfWork. + * + * @return array + */ + public function getScheduledDocumentUpdates() + { + return $this->_documentUpdates; + } + + /** + * Gets the currently scheduled document deletions in this UnitOfWork. + * + * @return array + */ + public function getScheduledDocumentDeletions() + { + return $this->_documentDeletions; + } +} diff --git a/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Version.php b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Version.php new file mode 100644 index 00000000000..6f6dc2b4a72 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/Doctrine/ODM/MongoDB/Version.php @@ -0,0 +1,51 @@ +. + */ + +namespace Doctrine\ODM\MongoDB; + +/** + * Class to store and retrieve the version of Doctrine + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @since 1.0 + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class Version +{ + /** + * Current Doctrine Version + */ + const VERSION = '1.0.0ALPHA1-DEV'; + + /** + * Compares a Doctrine version with the current one. + * + * @param string $version Doctrine version to compare. + * @return int Returns -1 if older, 0 if it is the same, 1 if version + * passed as argument is newer. + */ + public static function compare($version) + { + $currentVersion = str_replace(' ', '', strtolower(self::VERSION)); + $version = str_replace(' ', '', $version); + + return version_compare($version, $currentVersion); + } +} \ No newline at end of file diff --git a/src/vendor/doctrine-mongodb/lib/vendor/Symfony/Components/Yaml/Dumper.php b/src/vendor/doctrine-mongodb/lib/vendor/Symfony/Components/Yaml/Dumper.php new file mode 100644 index 00000000000..2db3f6cf1e0 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/vendor/Symfony/Components/Yaml/Dumper.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Dumper dumps PHP variables to YAML strings. + * + * @package symfony + * @subpackage yaml + * @author Fabien Potencier + */ +class Dumper +{ + /** + * Dumps a PHP value to YAML. + * + * @param mixed $input The PHP value + * @param integer $inline The level where you switch to inline YAML + * @param integer $indent The level o indentation indentation (used internally) + * + * @return string The YAML representation of the PHP value + */ + public function dump($input, $inline = 0, $indent = 0) + { + $output = ''; + $prefix = $indent ? str_repeat(' ', $indent) : ''; + + if ($inline <= 0 || !is_array($input) || empty($input)) + { + $output .= $prefix.Inline::dump($input); + } + else + { + $isAHash = array_keys($input) !== range(0, count($input) - 1); + + foreach ($input as $key => $value) + { + $willBeInlined = $inline - 1 <= 0 || !is_array($value) || empty($value); + + $output .= sprintf('%s%s%s%s', + $prefix, + $isAHash ? Inline::dump($key).':' : '-', + $willBeInlined ? ' ' : "\n", + $this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + 2) + ).($willBeInlined ? "\n" : ''); + } + } + + return $output; + } +} diff --git a/src/vendor/doctrine-mongodb/lib/vendor/Symfony/Components/Yaml/Exception.php b/src/vendor/doctrine-mongodb/lib/vendor/Symfony/Components/Yaml/Exception.php new file mode 100644 index 00000000000..49cb7949ee6 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/vendor/Symfony/Components/Yaml/Exception.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Exception class used by all exceptions thrown by the component. + * + * @package symfony + * @subpackage yaml + * @author Fabien Potencier + */ +class Exception extends \Exception +{ +} diff --git a/src/vendor/doctrine-mongodb/lib/vendor/Symfony/Components/Yaml/Inline.php b/src/vendor/doctrine-mongodb/lib/vendor/Symfony/Components/Yaml/Inline.php new file mode 100644 index 00000000000..854f4bba597 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/vendor/Symfony/Components/Yaml/Inline.php @@ -0,0 +1,410 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Inline implements a YAML parser/dumper for the YAML inline syntax. + * + * @package symfony + * @subpackage yaml + * @author Fabien Potencier + */ +class Inline +{ + const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\']*(?:\'\'[^\']*)*)\')'; + + /** + * Convert a YAML string to a PHP array. + * + * @param string $value A YAML string + * + * @return array A PHP array representing the YAML string + */ + static public function load($value) + { + $value = trim($value); + + if (0 == strlen($value)) + { + return ''; + } + + switch ($value[0]) + { + case '[': + return self::parseSequence($value); + case '{': + return self::parseMapping($value); + default: + return self::parseScalar($value); + } + } + + /** + * Dumps a given PHP variable to a YAML string. + * + * @param mixed $value The PHP variable to convert + * + * @return string The YAML string representing the PHP array + */ + static public function dump($value) + { + $trueValues = '1.1' == Yaml::getSpecVersion() ? array('true', 'on', '+', 'yes', 'y') : array('true'); + $falseValues = '1.1' == Yaml::getSpecVersion() ? array('false', 'off', '-', 'no', 'n') : array('false'); + + switch (true) + { + case is_resource($value): + throw new Exception('Unable to dump PHP resources in a YAML file.'); + case is_object($value): + return '!!php/object:'.serialize($value); + case is_array($value): + return self::dumpArray($value); + case null === $value: + return 'null'; + case true === $value: + return 'true'; + case false === $value: + return 'false'; + case ctype_digit($value): + return is_string($value) ? "'$value'" : (int) $value; + case is_numeric($value): + return is_infinite($value) ? str_ireplace('INF', '.Inf', strval($value)) : (is_string($value) ? "'$value'" : $value); + case false !== strpos($value, "\n") || false !== strpos($value, "\r"): + return sprintf('"%s"', str_replace(array('"', "\n", "\r"), array('\\"', '\n', '\r'), $value)); + case preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \# \?] | \A[ - ? | < > = ! % @ ` ]/x', $value): + return sprintf("'%s'", str_replace('\'', '\'\'', $value)); + case '' == $value: + return "''"; + case preg_match(self::getTimestampRegex(), $value): + return "'$value'"; + case in_array(strtolower($value), $trueValues): + return "'$value'"; + case in_array(strtolower($value), $falseValues): + return "'$value'"; + case in_array(strtolower($value), array('null', '~')): + return "'$value'"; + default: + return $value; + } + } + + /** + * Dumps a PHP array to a YAML string. + * + * @param array $value The PHP array to dump + * + * @return string The YAML string representing the PHP array + */ + static protected function dumpArray($value) + { + // array + $keys = array_keys($value); + if ( + (1 == count($keys) && '0' == $keys[0]) + || + (count($keys) > 1 && array_reduce($keys, function ($v, $w) { return (integer) $v + $w; }, 0) == count($keys) * (count($keys) - 1) / 2)) + { + $output = array(); + foreach ($value as $val) + { + $output[] = self::dump($val); + } + + return sprintf('[%s]', implode(', ', $output)); + } + + // mapping + $output = array(); + foreach ($value as $key => $val) + { + $output[] = sprintf('%s: %s', self::dump($key), self::dump($val)); + } + + return sprintf('{ %s }', implode(', ', $output)); + } + + /** + * Parses a scalar to a YAML string. + * + * @param scalar $scalar + * @param string $delimiters + * @param array $stringDelimiter + * @param integer $i + * @param boolean $evaluate + * + * @return string A YAML string + */ + static public function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true) + { + if (in_array($scalar[$i], $stringDelimiters)) + { + // quoted scalar + $output = self::parseQuotedScalar($scalar, $i); + } + else + { + // "normal" string + if (!$delimiters) + { + $output = substr($scalar, $i); + $i += strlen($output); + + // remove comments + if (false !== $strpos = strpos($output, ' #')) + { + $output = rtrim(substr($output, 0, $strpos)); + } + } + else if (preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) + { + $output = $match[1]; + $i += strlen($output); + } + else + { + throw new ParserException(sprintf('Malformed inline YAML string (%s).', $scalar)); + } + + $output = $evaluate ? self::evaluateScalar($output) : $output; + } + + return $output; + } + + /** + * Parses a quoted scalar to YAML. + * + * @param string $scalar + * @param integer $i + * + * @return string A YAML string + */ + static protected function parseQuotedScalar($scalar, &$i) + { + if (!preg_match('/'.self::REGEX_QUOTED_STRING.'/A', substr($scalar, $i), $match)) + { + throw new ParserException(sprintf('Malformed inline YAML string (%s).', substr($scalar, $i))); + } + + $output = substr($match[0], 1, strlen($match[0]) - 2); + + if ('"' == $scalar[$i]) + { + // evaluate the string + $output = str_replace(array('\\"', '\\n', '\\r'), array('"', "\n", "\r"), $output); + } + else + { + // unescape ' + $output = str_replace('\'\'', '\'', $output); + } + + $i += strlen($match[0]); + + return $output; + } + + /** + * Parses a sequence to a YAML string. + * + * @param string $sequence + * @param integer $i + * + * @return string A YAML string + */ + static protected function parseSequence($sequence, &$i = 0) + { + $output = array(); + $len = strlen($sequence); + $i += 1; + + // [foo, bar, ...] + while ($i < $len) + { + switch ($sequence[$i]) + { + case '[': + // nested sequence + $output[] = self::parseSequence($sequence, $i); + break; + case '{': + // nested mapping + $output[] = self::parseMapping($sequence, $i); + break; + case ']': + return $output; + case ',': + case ' ': + break; + default: + $isQuoted = in_array($sequence[$i], array('"', "'")); + $value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i); + + if (!$isQuoted && false !== strpos($value, ': ')) + { + // embedded mapping? + try + { + $value = self::parseMapping('{'.$value.'}'); + } + catch (\InvalidArgumentException $e) + { + // no, it's not + } + } + + $output[] = $value; + + --$i; + } + + ++$i; + } + + throw new ParserException(sprintf('Malformed inline YAML string %s', $sequence)); + } + + /** + * Parses a mapping to a YAML string. + * + * @param string $mapping + * @param integer $i + * + * @return string A YAML string + */ + static protected function parseMapping($mapping, &$i = 0) + { + $output = array(); + $len = strlen($mapping); + $i += 1; + + // {foo: bar, bar:foo, ...} + while ($i < $len) + { + switch ($mapping[$i]) + { + case ' ': + case ',': + ++$i; + continue 2; + case '}': + return $output; + } + + // key + $key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false); + + // value + $done = false; + while ($i < $len) + { + switch ($mapping[$i]) + { + case '[': + // nested sequence + $output[$key] = self::parseSequence($mapping, $i); + $done = true; + break; + case '{': + // nested mapping + $output[$key] = self::parseMapping($mapping, $i); + $done = true; + break; + case ':': + case ' ': + break; + default: + $output[$key] = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i); + $done = true; + --$i; + } + + ++$i; + + if ($done) + { + continue 2; + } + } + } + + throw new ParserException(sprintf('Malformed inline YAML string %s', $mapping)); + } + + /** + * Evaluates scalars and replaces magic values. + * + * @param string $scalar + * + * @return string A YAML string + */ + static protected function evaluateScalar($scalar) + { + $scalar = trim($scalar); + + $trueValues = '1.1' == Yaml::getSpecVersion() ? array('true', 'on', '+', 'yes', 'y') : array('true'); + $falseValues = '1.1' == Yaml::getSpecVersion() ? array('false', 'off', '-', 'no', 'n') : array('false'); + + switch (true) + { + case 'null' == strtolower($scalar): + case '' == $scalar: + case '~' == $scalar: + return null; + case 0 === strpos($scalar, '!str'): + return (string) substr($scalar, 5); + case 0 === strpos($scalar, '! '): + return intval(self::parseScalar(substr($scalar, 2))); + case 0 === strpos($scalar, '!!php/object:'): + return unserialize(substr($scalar, 13)); + case ctype_digit($scalar): + $raw = $scalar; + $cast = intval($scalar); + return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw); + case in_array(strtolower($scalar), $trueValues): + return true; + case in_array(strtolower($scalar), $falseValues): + return false; + case is_numeric($scalar): + return '0x' == $scalar[0].$scalar[1] ? hexdec($scalar) : floatval($scalar); + case 0 == strcasecmp($scalar, '.inf'): + case 0 == strcasecmp($scalar, '.NaN'): + return -log(0); + case 0 == strcasecmp($scalar, '-.inf'): + return log(0); + case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar): + return floatval(str_replace(',', '', $scalar)); + case preg_match(self::getTimestampRegex(), $scalar): + return strtotime($scalar); + default: + return (string) $scalar; + } + } + + static protected function getTimestampRegex() + { + return <<[0-9][0-9][0-9][0-9]) + -(?P[0-9][0-9]?) + -(?P[0-9][0-9]?) + (?:(?:[Tt]|[ \t]+) + (?P[0-9][0-9]?) + :(?P[0-9][0-9]) + :(?P[0-9][0-9]) + (?:\.(?P[0-9]*))? + (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) + (?::(?P[0-9][0-9]))?))?)? + $~x +EOF; + } +} diff --git a/src/vendor/doctrine-mongodb/lib/vendor/Symfony/Components/Yaml/Parser.php b/src/vendor/doctrine-mongodb/lib/vendor/Symfony/Components/Yaml/Parser.php new file mode 100644 index 00000000000..330f9a1ef61 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/vendor/Symfony/Components/Yaml/Parser.php @@ -0,0 +1,587 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Parser parses YAML strings to convert them to PHP arrays. + * + * @package symfony + * @subpackage yaml + * @author Fabien Potencier + */ +class Parser +{ + protected $offset = 0; + protected $lines = array(); + protected $currentLineNb = -1; + protected $currentLine = ''; + protected $refs = array(); + + /** + * Constructor + * + * @param integer $offset The offset of YAML document (used for line numbers in error messages) + */ + public function __construct($offset = 0) + { + $this->offset = $offset; + } + + /** + * Parses a YAML string to a PHP value. + * + * @param string $value A YAML string + * + * @return mixed A PHP value + * + * @throws \InvalidArgumentException If the YAML is not valid + */ + public function parse($value) + { + $this->currentLineNb = -1; + $this->currentLine = ''; + $this->lines = explode("\n", $this->cleanup($value)); + + $data = array(); + while ($this->moveToNextLine()) + { + if ($this->isCurrentLineEmpty()) + { + continue; + } + + // tab? + if (preg_match('#^\t+#', $this->currentLine)) + { + throw new ParserException(sprintf('A YAML file cannot contain tabs as indentation at line %d (%s).', $this->getRealCurrentLineNb() + 1, $this->currentLine)); + } + + $isRef = $isInPlace = $isProcessed = false; + if (preg_match('#^\-((?P\s+)(?P.+?))?\s*$#', $this->currentLine, $values)) + { + if (isset($values['value']) && preg_match('#^&(?P[^ ]+) *(?P.*)#', $values['value'], $matches)) + { + $isRef = $matches['ref']; + $values['value'] = $matches['value']; + } + + // array + if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) + { + $c = $this->getRealCurrentLineNb() + 1; + $parser = new Parser($c); + $parser->refs =& $this->refs; + $data[] = $parser->parse($this->getNextEmbedBlock()); + } + else + { + if (isset($values['leadspaces']) + && ' ' == $values['leadspaces'] + && preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{].*?) *\:(\s+(?P.+?))?\s*$#', $values['value'], $matches)) + { + // this is a compact notation element, add to next block and parse + $c = $this->getRealCurrentLineNb(); + $parser = new Parser($c); + $parser->refs =& $this->refs; + + $block = $values['value']; + if (!$this->isNextLineIndented()) + { + $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + 2); + } + + $data[] = $parser->parse($block); + } + else + { + $data[] = $this->parseValue($values['value']); + } + } + } + else if (preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"].*?) *\:(\s+(?P.+?))?\s*$#', $this->currentLine, $values)) + { + $key = Inline::parseScalar($values['key']); + + if ('<<' === $key) + { + if (isset($values['value']) && '*' === substr($values['value'], 0, 1)) + { + $isInPlace = substr($values['value'], 1); + if (!array_key_exists($isInPlace, $this->refs)) + { + throw new ParserException(sprintf('Reference "%s" does not exist at line %s (%s).', $isInPlace, $this->getRealCurrentLineNb() + 1, $this->currentLine)); + } + } + else + { + if (isset($values['value']) && $values['value'] !== '') + { + $value = $values['value']; + } + else + { + $value = $this->getNextEmbedBlock(); + } + $c = $this->getRealCurrentLineNb() + 1; + $parser = new Parser($c); + $parser->refs =& $this->refs; + $parsed = $parser->parse($value); + + $merged = array(); + if (!is_array($parsed)) + { + throw new ParserException(sprintf("YAML merge keys used with a scalar value instead of an array at line %s (%s)", $this->getRealCurrentLineNb() + 1, $this->currentLine)); + } + else if (isset($parsed[0])) + { + // Numeric array, merge individual elements + foreach (array_reverse($parsed) as $parsedItem) + { + if (!is_array($parsedItem)) + { + throw new ParserException(sprintf("Merge items must be arrays at line %s (%s).", $this->getRealCurrentLineNb() + 1, $parsedItem)); + } + $merged = array_merge($parsedItem, $merged); + } + } + else + { + // Associative array, merge + $merged = array_merge($merge, $parsed); + } + + $isProcessed = $merged; + } + } + else if (isset($values['value']) && preg_match('#^&(?P[^ ]+) *(?P.*)#', $values['value'], $matches)) + { + $isRef = $matches['ref']; + $values['value'] = $matches['value']; + } + + if ($isProcessed) + { + // Merge keys + $data = $isProcessed; + } + // hash + else if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) + { + // if next line is less indented or equal, then it means that the current value is null + if ($this->isNextLineIndented()) + { + $data[$key] = null; + } + else + { + $c = $this->getRealCurrentLineNb() + 1; + $parser = new Parser($c); + $parser->refs =& $this->refs; + $data[$key] = $parser->parse($this->getNextEmbedBlock()); + } + } + else + { + if ($isInPlace) + { + $data = $this->refs[$isInPlace]; + } + else + { + $data[$key] = $this->parseValue($values['value']); + } + } + } + else + { + // 1-liner followed by newline + if (2 == count($this->lines) && empty($this->lines[1])) + { + $value = Inline::load($this->lines[0]); + if (is_array($value)) + { + $first = reset($value); + if ('*' === substr($first, 0, 1)) + { + $data = array(); + foreach ($value as $alias) + { + $data[] = $this->refs[substr($alias, 1)]; + } + $value = $data; + } + } + + return $value; + } + + switch (preg_last_error()) + { + case PREG_INTERNAL_ERROR: + $error = 'Internal PCRE error on line'; + break; + case PREG_BACKTRACK_LIMIT_ERROR: + $error = 'pcre.backtrack_limit reached on line'; + break; + case PREG_RECURSION_LIMIT_ERROR: + $error = 'pcre.recursion_limit reached on line'; + break; + case PREG_BAD_UTF8_ERROR: + $error = 'Malformed UTF-8 data on line'; + break; + case PREG_BAD_UTF8_OFFSET_ERROR: + $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point on line'; + break; + default: + $error = 'Unable to parse line'; + } + + throw new ParserException(sprintf('%s %d (%s).', $error, $this->getRealCurrentLineNb() + 1, $this->currentLine)); + } + + if ($isRef) + { + $this->refs[$isRef] = end($data); + } + } + + return empty($data) ? null : $data; + } + + /** + * Returns the current line number (takes the offset into account). + * + * @return integer The current line number + */ + protected function getRealCurrentLineNb() + { + return $this->currentLineNb + $this->offset; + } + + /** + * Returns the current line indentation. + * + * @return integer The current line indentation + */ + protected function getCurrentLineIndentation() + { + return strlen($this->currentLine) - strlen(ltrim($this->currentLine, ' ')); + } + + /** + * Returns the next embed block of YAML. + * + * @param integer $indentation The indent level at which the block is to be read, or null for default + * + * @return string A YAML string + */ + protected function getNextEmbedBlock($indentation = null) + { + $this->moveToNextLine(); + + if (null === $indentation) + { + $newIndent = $this->getCurrentLineIndentation(); + + if (!$this->isCurrentLineEmpty() && 0 == $newIndent) + { + throw new ParserException(sprintf('Indentation problem at line %d (%s)', $this->getRealCurrentLineNb() + 1, $this->currentLine)); + } + } + else + { + $newIndent = $indentation; + } + + $data = array(substr($this->currentLine, $newIndent)); + + while ($this->moveToNextLine()) + { + if ($this->isCurrentLineEmpty()) + { + if ($this->isCurrentLineBlank()) + { + $data[] = substr($this->currentLine, $newIndent); + } + + continue; + } + + $indent = $this->getCurrentLineIndentation(); + + if (preg_match('#^(?P *)$#', $this->currentLine, $match)) + { + // empty line + $data[] = $match['text']; + } + else if ($indent >= $newIndent) + { + $data[] = substr($this->currentLine, $newIndent); + } + else if (0 == $indent) + { + $this->moveToPreviousLine(); + + break; + } + else + { + throw new ParserException(sprintf('Indentation problem at line %d (%s)', $this->getRealCurrentLineNb() + 1, $this->currentLine)); + } + } + + return implode("\n", $data); + } + + /** + * Moves the parser to the next line. + */ + protected function moveToNextLine() + { + if ($this->currentLineNb >= count($this->lines) - 1) + { + return false; + } + + $this->currentLine = $this->lines[++$this->currentLineNb]; + + return true; + } + + /** + * Moves the parser to the previous line. + */ + protected function moveToPreviousLine() + { + $this->currentLine = $this->lines[--$this->currentLineNb]; + } + + /** + * Parses a YAML value. + * + * @param string $value A YAML value + * + * @return mixed A PHP value + */ + protected function parseValue($value) + { + if ('*' === substr($value, 0, 1)) + { + if (false !== $pos = strpos($value, '#')) + { + $value = substr($value, 1, $pos - 2); + } + else + { + $value = substr($value, 1); + } + + if (!array_key_exists($value, $this->refs)) + { + throw new ParserException(sprintf('Reference "%s" does not exist (%s).', $value, $this->currentLine)); + } + return $this->refs[$value]; + } + + if (preg_match('/^(?P\||>)(?P\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P +#.*)?$/', $value, $matches)) + { + $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : ''; + + return $this->parseFoldedScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), intval(abs($modifiers))); + } + else + { + return Inline::load($value); + } + } + + /** + * Parses a folded scalar. + * + * @param string $separator The separator that was used to begin this folded scalar (| or >) + * @param string $indicator The indicator that was used to begin this folded scalar (+ or -) + * @param integer $indentation The indentation that was used to begin this folded scalar + * + * @return string The text value + */ + protected function parseFoldedScalar($separator, $indicator = '', $indentation = 0) + { + $separator = '|' == $separator ? "\n" : ' '; + $text = ''; + + $notEOF = $this->moveToNextLine(); + + while ($notEOF && $this->isCurrentLineBlank()) + { + $text .= "\n"; + + $notEOF = $this->moveToNextLine(); + } + + if (!$notEOF) + { + return ''; + } + + if (!preg_match('#^(?P'.($indentation ? str_repeat(' ', $indentation) : ' +').')(?P.*)$#', $this->currentLine, $matches)) + { + $this->moveToPreviousLine(); + + return ''; + } + + $textIndent = $matches['indent']; + $previousIndent = 0; + + $text .= $matches['text'].$separator; + while ($this->currentLineNb + 1 < count($this->lines)) + { + $this->moveToNextLine(); + + if (preg_match('#^(?P {'.strlen($textIndent).',})(?P.+)$#', $this->currentLine, $matches)) + { + if (' ' == $separator && $previousIndent != $matches['indent']) + { + $text = substr($text, 0, -1)."\n"; + } + $previousIndent = $matches['indent']; + + $text .= str_repeat(' ', $diff = strlen($matches['indent']) - strlen($textIndent)).$matches['text'].($diff ? "\n" : $separator); + } + else if (preg_match('#^(?P *)$#', $this->currentLine, $matches)) + { + $text .= preg_replace('#^ {1,'.strlen($textIndent).'}#', '', $matches['text'])."\n"; + } + else + { + $this->moveToPreviousLine(); + + break; + } + } + + if (' ' == $separator) + { + // replace last separator by a newline + $text = preg_replace('/ (\n*)$/', "\n$1", $text); + } + + switch ($indicator) + { + case '': + $text = preg_replace('#\n+$#s', "\n", $text); + break; + case '+': + break; + case '-': + $text = preg_replace('#\n+$#s', '', $text); + break; + } + + return $text; + } + + /** + * Returns true if the next line is indented. + * + * @return Boolean Returns true if the next line is indented, false otherwise + */ + protected function isNextLineIndented() + { + $currentIndentation = $this->getCurrentLineIndentation(); + $notEOF = $this->moveToNextLine(); + + while ($notEOF && $this->isCurrentLineEmpty()) + { + $notEOF = $this->moveToNextLine(); + } + + if (false === $notEOF) + { + return false; + } + + $ret = false; + if ($this->getCurrentLineIndentation() <= $currentIndentation) + { + $ret = true; + } + + $this->moveToPreviousLine(); + + return $ret; + } + + /** + * Returns true if the current line is blank or if it is a comment line. + * + * @return Boolean Returns true if the current line is empty or if it is a comment line, false otherwise + */ + protected function isCurrentLineEmpty() + { + return $this->isCurrentLineBlank() || $this->isCurrentLineComment(); + } + + /** + * Returns true if the current line is blank. + * + * @return Boolean Returns true if the current line is blank, false otherwise + */ + protected function isCurrentLineBlank() + { + return '' == trim($this->currentLine, ' '); + } + + /** + * Returns true if the current line is a comment line. + * + * @return Boolean Returns true if the current line is a comment line, false otherwise + */ + protected function isCurrentLineComment() + { + //checking explicitly the first char of the trim is faster than loops or strpos + $ltrimmedLine = ltrim($this->currentLine, ' '); + return $ltrimmedLine[0] === '#'; + } + + /** + * Cleanups a YAML string to be parsed. + * + * @param string $value The input YAML string + * + * @return string A cleaned up YAML string + */ + protected function cleanup($value) + { + $value = str_replace(array("\r\n", "\r"), "\n", $value); + + if (!preg_match("#\n$#", $value)) + { + $value .= "\n"; + } + + // strip YAML header + $count = 0; + $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#s', '', $value, -1, $count); + $this->offset += $count; + + // remove leading comments and/or --- + $trimmedValue = preg_replace('#^((\#.*?\n)|(\-\-\-.*?\n))*#s', '', $value, -1, $count); + if ($count == 1) + { + // items have been removed, update the offset + $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); + $value = $trimmedValue; + } + + return $value; + } +} diff --git a/src/vendor/doctrine-mongodb/lib/vendor/Symfony/Components/Yaml/ParserException.php b/src/vendor/doctrine-mongodb/lib/vendor/Symfony/Components/Yaml/ParserException.php new file mode 100644 index 00000000000..5ba22291de9 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/vendor/Symfony/Components/Yaml/ParserException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Exception class used by all exceptions thrown by the component. + * + * @package symfony + * @subpackage yaml + * @author Fabien Potencier + */ +class ParserException extends Exception +{ +} diff --git a/src/vendor/doctrine-mongodb/lib/vendor/Symfony/Components/Yaml/Yaml.php b/src/vendor/doctrine-mongodb/lib/vendor/Symfony/Components/Yaml/Yaml.php new file mode 100644 index 00000000000..ff8671d5fe6 --- /dev/null +++ b/src/vendor/doctrine-mongodb/lib/vendor/Symfony/Components/Yaml/Yaml.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Yaml offers convenience methods to load and dump YAML. + * + * @package symfony + * @subpackage yaml + * @author Fabien Potencier + */ +class Yaml +{ + static protected $spec = '1.2'; + + /** + * Sets the YAML specification version to use. + * + * @param string $version The YAML specification version + */ + static public function setSpecVersion($version) + { + if (!in_array($version, array('1.1', '1.2'))) + { + throw new \InvalidArgumentException(sprintf('Version %s of the YAML specifications is not supported', $version)); + } + + self::$spec = $version; + } + + /** + * Gets the YAML specification version to use. + * + * @return string The YAML specification version + */ + static public function getSpecVersion() + { + return self::$spec; + } + + /** + * Loads YAML into a PHP array. + * + * The load method, when supplied with a YAML stream (string or file), + * will do its best to convert YAML in a file into a PHP array. + * + * Usage: + * + * $array = Yaml::load('config.yml'); + * print_r($array); + * + * + * @param string $input Path of YAML file or string containing YAML + * + * @return array The YAML converted to a PHP array + * + * @throws \InvalidArgumentException If the YAML is not valid + */ + public static function load($input) + { + $file = ''; + + // if input is a file, process it + if (strpos($input, "\n") === false && is_file($input)) + { + $file = $input; + + ob_start(); + $retval = include($input); + $content = ob_get_clean(); + + // if an array is returned by the config file assume it's in plain php form else in YAML + $input = is_array($retval) ? $retval : $content; + } + + // if an array is returned by the config file assume it's in plain php form else in YAML + if (is_array($input)) + { + return $input; + } + + $yaml = new Parser(); + + try + { + $ret = $yaml->parse($input); + } + catch (\Exception $e) + { + throw new \InvalidArgumentException(sprintf('Unable to parse %s: %s', $file ? sprintf('file "%s"', $file) : 'string', $e->getMessage())); + } + + return $ret; + } + + /** + * Dumps a PHP array to a YAML string. + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. + * + * @param array $array PHP array + * @param integer $inline The level where you switch to inline YAML + * + * @return string A YAML string representing the original PHP array + */ + public static function dump($array, $inline = 2) + { + $yaml = new Dumper(); + + return $yaml->dump($array, $inline); + } +} diff --git a/src/vendor/symfony/src/Symfony/Framework/DoctrineMongoDBBundle/DependencyInjection/MongoDBExtension.php b/src/vendor/symfony/src/Symfony/Framework/DoctrineMongoDBBundle/DependencyInjection/MongoDBExtension.php new file mode 100755 index 00000000000..a9df2cd8616 --- /dev/null +++ b/src/vendor/symfony/src/Symfony/Framework/DoctrineMongoDBBundle/DependencyInjection/MongoDBExtension.php @@ -0,0 +1,179 @@ + + * @author Kris Wallsmith + * @author Jonathan H. Wage + * + * @todo Add support for multiple document managers + */ +class MongoDBExtension extends LoaderExtension +{ + protected $bundles; + protected $resources = array( + 'mongodb' => 'mongodb.xml', + ); + + public function __construct(array $bundles) + { + $this->bundles = $bundles; + } + + public function mongodbLoad($config, BuilderConfiguration $configuration) + { + $loader = new XmlFileLoader(__DIR__.'/../Resources/config'); + $configuration->merge($loader->load($this->resources['mongodb'])); + + if (!$configuration->hasDefinition('doctrine.odm.mongodb.document_manager')) { + + $configuration->setParameter('doctrine.odm.mongodb.mapping_dirs', $this->findBundleSubpaths('Resources/config/doctrine/metadata', $configuration)); + $configuration->setParameter('doctrine.odm.mongodb.document_dirs', $this->findBundleSubpaths('Document', $configuration)); + + $configuration->setDefinition('doctrine.odm.mongodb.metadata', $this->buildMetadataDefinition($configuration)); + } + + foreach (array('host', 'port', 'database') as $key) { + if (isset($config[$key])) { + $configuration->setParameter('doctrine.odm.mongodb.default_'.$key, $config[$key]); + } + } + + foreach (array('proxy_dir', 'auto_generate_proxy_classes') as $key) { + if (isset($config[$key])) { + $configuration->setParameter('doctrine.odm.mongodb.'.$key, $config[$key]); + } + } + + foreach (array('cache', 'metadata') as $key) { + if (isset($config[$key])) { + $configuration->setAlias('doctrine.odm.mongodb.'.$key, 'doctrine.odm.mongodb.'.$key.'.'.$config[$key]); + } + } + + return $configuration; + } + + /** + * Finds existing bundle subpaths. + * + * @param string $path A subpath to check for + * @param Symfony\Components\DependencyInjection\BuilderConfiguration $configuration A builder configuration + * + * @return array An array of absolute directory paths + */ + protected function findBundleSubpaths($path, BuilderConfiguration $configuration) + { + $dirs = array(); + foreach ($this->bundles as $bundle) { + $reflection = new \ReflectionClass($bundle); + if (is_dir($dir = dirname($reflection->getFilename()).'/'.$path)) { + $dirs[] = $dir; + $configuration->addResource(new FileResource($dir)); + } else { + // add the closest existing parent directory as a file resource + do { + $dir = dirname($dir); + } while (!is_dir($dir)); + $configuration->addResource(new FileResource($dir)); + } + } + + return $dirs; + } + + /** + * Detects and builds the appropriate metadata driver for each bundle. + * + * @param Symfony\Components\DependencyInjection\BuilderConfiguration $configuration A builder configuration + * + * @return Symfony\Components\DependencyInjection\Definition A definition for the metadata service + */ + protected function buildMetadataDefinition(BuilderConfiguration $configuration) + { + $definition = new Definition('%doctrine.odm.mongodb.metadata.chain_class%'); + + foreach ($this->bundles as $bundle) { + $reflection = new \ReflectionClass($bundle); + if ($driver = static::detectMetadataDriver(dirname($reflection->getFilename()), $configuration)) { + $definition->addMethodCall('addDriver', array( + new Reference('doctrine.odm.mongodb.metadata.'.$driver), + $reflection->getNamespaceName().'\\Document', + )); + } + } + + return $definition; + } + + /** + * Detects what metadata driver to use for the supplied directory. + * + * @param string $dir A directory path + * @param Symfony\Components\DependencyInjection\BuilderConfiguration $configuration A builder configuration + * + * @return string|null A metadata driver short name, if one can be detected + */ + static protected function detectMetadataDriver($dir, BuilderConfiguration $configuration) + { + // add the closest existing directory as a resource + $resource = $dir.'/Resources/config/doctrine/metadata'; + while (!is_dir($resource)) { + $resource = dirname($resource); + } + $configuration->addResource(new FileResource($resource)); + + if (count(glob($dir.'/Resources/config/doctrine/metadata/*.xml'))) { + return 'xml'; + } elseif (count(glob($dir.'/Resources/config/doctrine/metadata/*.yml'))) { + return 'yml'; + } + + // add the directory itself as a resource + $configuration->addResource(new FileResource($dir)); + + if (is_dir($dir.'/Document')) { + return 'annotation'; + } + } + + /** + * Returns the namespace to be used for this extension (XML namespace). + * + * @return string The XML namespace + */ + public function getNamespace() + { + return 'http://www.symfony-project.org/schema/dic/doctrine/odm/mongodb'; + } + + /** + * @return string + */ + public function getXsdValidationBasePath() + { + return __DIR__.'/../Resources/config'; + } + + /** + * Returns the recommended alias to use in XML. + * + * This alias is also the mandatory prefix to use when using YAML. + * + * @return string The alias + */ + public function getAlias() + { + return 'doctrine_odm'; + } +} \ No newline at end of file diff --git a/src/vendor/symfony/src/Symfony/Framework/DoctrineMongoDBBundle/DoctrineMongoDBBundle.php b/src/vendor/symfony/src/Symfony/Framework/DoctrineMongoDBBundle/DoctrineMongoDBBundle.php new file mode 100755 index 00000000000..2770feef1bd --- /dev/null +++ b/src/vendor/symfony/src/Symfony/Framework/DoctrineMongoDBBundle/DoctrineMongoDBBundle.php @@ -0,0 +1,23 @@ + + * @author Kris Wallsmith + * @author Jonathan H. Wage + */ +class DoctrineMongoDBBundle extends Bundle +{ + public function buildContainer(ContainerInterface $container) + { + Loader::registerExtension(new MongoDBExtension($container->getParameter('kernel.bundles'))); + } +} \ No newline at end of file diff --git a/src/vendor/symfony/src/Symfony/Framework/DoctrineMongoDBBundle/Resources/config/mongodb.xml b/src/vendor/symfony/src/Symfony/Framework/DoctrineMongoDBBundle/Resources/config/mongodb.xml new file mode 100755 index 00000000000..d5d73212c8a --- /dev/null +++ b/src/vendor/symfony/src/Symfony/Framework/DoctrineMongoDBBundle/Resources/config/mongodb.xml @@ -0,0 +1,101 @@ + + + + + + Doctrine\ODM\MongoDB\Mongo + Doctrine\ODM\MongoDB\Configuration + Doctrine\ODM\MongoDB\DocumentManager + + + %doctrine.odm.mongodb.default_host%:%doctrine.odm.mongodb.default_port% + localhost + 27017 + %kernel.name%_%kernel.environment% + true + + + %kernel.cache_dir%/Proxies + Proxies + false + + + Doctrine\Common\Cache\ArrayCache + Doctrine\Common\Cache\ApcCache + Doctrine\Common\Cache\MemcacheCache + localhost + 11211 + Memcache + Doctrine\Common\Cache\XcacheCache + + + Doctrine\ODM\MongoDB\Mapping\Driver\DriverChain + Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver + Doctrine\Common\Annotations\AnnotationReader + Doctrine\ODM\MongoDB\Mapping\ + Doctrine\ODM\MongoDB\Mapping\Driver\XmlDriver + Doctrine\ODM\MongoDB\Mapping\Driver\YamlDriver + + + + %doctrine.odm.mongodb.mapping_dirs% + %doctrine.odm.mongodb.mapping_dirs% + + + + + + + + + + + + + %doctrine.odm.mongodb.document_dirs% + + + + %doctrine.odm.mongodb.metadata.annotation_default_namespace% + + %doctrine.odm.mongodb.xml_mapping_dirs% + %doctrine.odm.mongodb.yml_mapping_dirs% + + + + + + + + + + %doctrine.odm.mongodb.cache.memcache_host% + %doctrine.odm.mongodb.cache.memcache_port% + + + + + + + %doctrine.odm.mongodb.default_server% + %doctrine.odm.mongodb.default_connection_options% + + + + + %doctrine.odm.mongodb.proxy_dir% + %doctrine.odm.mongodb.proxy_namespace% + %doctrine.odm.mongodb.auto_generate_proxy_classes% + + %doctrine.odm.mongodb.default_database% + + + + + + + + + diff --git a/src/vendor/symfony/src/Symfony/Framework/DoctrineMongoDBBundle/Resources/config/schema/dic/doctrine/odm/mongodb/mongodb-1.0.xsd b/src/vendor/symfony/src/Symfony/Framework/DoctrineMongoDBBundle/Resources/config/schema/dic/doctrine/odm/mongodb/mongodb-1.0.xsd new file mode 100644 index 00000000000..86649edf84c --- /dev/null +++ b/src/vendor/symfony/src/Symfony/Framework/DoctrineMongoDBBundle/Resources/config/schema/dic/doctrine/odm/mongodb/mongodb-1.0.xsd @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/bundles/framework/css/exception.css b/web/bundles/framework/css/exception.css new file mode 100644 index 00000000000..cce6639070e --- /dev/null +++ b/web/bundles/framework/css/exception.css @@ -0,0 +1,38 @@ +/* +Copyright (c) 2010, Yahoo! Inc. All rights reserved. +Code licensed under the BSD License: +http://developer.yahoo.com/yui/license.html +version: 3.1.2 +build: 56 +*/ +.sf-exceptionreset html{color:#000;background:#FFF;}.sf-exceptionreset body,.sf-exceptionreset div,.sf-exceptionreset dl,.sf-exceptionreset dt,.sf-exceptionreset dd,.sf-exceptionreset ul,.sf-exceptionreset ol,.sf-exceptionreset li,.sf-exceptionreset h1,.sf-exceptionreset h2,.sf-exceptionreset h3,.sf-exceptionreset h4,.sf-exceptionreset h5,.sf-exceptionreset h6,.sf-exceptionreset pre,.sf-exceptionreset code,.sf-exceptionreset form,.sf-exceptionreset fieldset,.sf-exceptionreset legend,.sf-exceptionreset input,.sf-exceptionreset textarea,.sf-exceptionreset p,.sf-exceptionreset blockquote,.sf-exceptionreset th,.sf-exceptionreset td{margin:0;padding:0;}.sf-exceptionreset table{border-collapse:collapse;border-spacing:0;}.sf-exceptionreset fieldset,.sf-exceptionreset img{border:0;}.sf-exceptionreset address,.sf-exceptionreset caption,.sf-exceptionreset cite,.sf-exceptionreset code,.sf-exceptionreset dfn,.sf-exceptionreset em,.sf-exceptionreset strong,.sf-exceptionreset th,.sf-exceptionreset var{font-style:normal;font-weight:normal;}.sf-exceptionreset li{list-style:none;}.sf-exceptionreset caption,.sf-exceptionreset th{text-align:left;}.sf-exceptionreset h1,.sf-exceptionreset h2,.sf-exceptionreset h3,.sf-exceptionreset h4,.sf-exceptionreset h5,.sf-exceptionreset h6{font-size:100%;font-weight:normal;}.sf-exceptionreset q:before,.sf-exceptionreset q:after{content:'';}.sf-exceptionreset abbr,.sf-exceptionreset acronym{border:0;font-variant:normal;}.sf-exceptionreset sup{vertical-align:text-top;}.sf-exceptionreset sub{vertical-align:text-bottom;}.sf-exceptionreset input,.sf-exceptionreset textarea,.sf-exceptionreset select{font-family:inherit;font-size:inherit;font-weight:inherit;}.sf-exceptionreset input,.sf-exceptionreset textarea,.sf-exceptionreset select{*font-size:100%;}.sf-exceptionreset legend{color:#000;} + +.sf-exceptionreset strong { font-weight: bold } +.sf-exceptionreset em { font-style: italic } +.sf-exceptionreset a { color: #333 } +.sf-exceptionreset abbr { border-bottom: 1px dotted #000000; cursor: help } +.sf-exceptionreset h1 { font-size: 170%; letter-spacing: -0.03em; } +.sf-exceptionreset h2 { margin-top: 4px; font-size: 90%; letter-spacing: -0.02em; } +.sf-exceptionreset h3 { font-size: 130%; font-weight: bold; letter-spacing: -0.02em; } +.sf-exceptionreset h3 span { float: right; font-size: 80%; background: #eee; color: #333; padding: 5px; -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; } +.sf-exceptionreset ul { padding-left: 20px } +.sf-exceptionreset ul li { padding-bottom: 5px } +.sf-exceptionreset ol { padding: 10px 0 } +.sf-exceptionreset ol li { list-style: decimal; margin-left: 20px; padding: 2px } +.sf-exceptionreset ol ol li { list-style-position: inside; margin-left: 0; white-space: nowrap } +.sf-exceptionreset li .selected { background-color: #ffd; padding: 4px 3px } +.sf-exceptionreset p.error { padding: 10px; background-color: #f00; font-weight: bold; text-align: center; -moz-border-radius: 10px; -webkit-border-radius: 10px; border-radius: 10px; } +.sf-exceptionreset p.error a { color: #fff } +.sf-exceptionreset .block { padding: 20px 25px; margin-bottom: 10px; border: 1px solid #ddd; background-color: #fff; text-align:left; -moz-border-radius: 10px; -webkit-border-radius: 10px; border-radius: 10px; width: 770px; box-shadow: 0 1px 2px rgba(0,0,0,0.15); -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.15); -moz-box-shadow: 0 1px 3px rgba(0,0,0,0.3); } +.sf-exceptionreset .traces { display: none; margin-top: 10px } +.sf-exceptionreset .trace { overflow: auto; width: 746px; } +.sf-exceptionreset #message { margin-top: 30px; margin-bottom: 10px; padding: 20px 25px; text-align:left; -moz-border-radius: 10px; -webkit-border-radius: 10px; border-radius: 10px; width: 770px; background-color: #c8e8f3; border: 1px solid #ddd; background-image: -moz-linear-gradient(-90deg, #fff, #c8e8f3); background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fff), to(#c8e8f3)); box-shadow: 0 1px 2px rgba(0,0,0,0.15); -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.15); -moz-box-shadow: 0 1px 3px rgba(0,0,0,0.3); } +.sf-exceptionreset #content { border: 1px solid #ddd; margin-top: 10px; padding: 7px; overflow: auto; } +.sf-exceptionreset a.file_link { text-decoration: none; } +.sf-exceptionreset a.file_link:hover { text-decoration: underline; } +.sf-exceptionreset code { font-size: 105%; font-family: monospace; overflow: auto; } +.sf-exceptionreset img { vertical-align: middle; } +.sf-exceptionreset .error { background-color: #f66 } +.sf-exceptionreset .linked ul, .sf-exceptionreset .linked li { padding-left: 0; display: inline } +.sf-exceptionreset .linked li { padding-right: 7px } +.sf-exceptionreset #logs { margin-top: 10px }