Migrating Schemas ================= Even though MongoDB is schemaless, introducing some kind of object mapper means that your object definitions become your schema. You may have a situation where you rename a property in your object model but need to load values from older documents where the field is still using the former name. While you could use MongoDB's `$rename`_ operator to migrate everything, sometimes a lazy migration is preferable. Doctrine offers a few different methods for dealing with this problem! .. note:: The features in this chapter were inspired by `Objectify`_, an object mapper for the Google App Engine datastore. Additional information may be found in the `Objectify schema migration`_ documentation. Renaming a Field ---------------- Let's say you have a simple document that starts off with the following fields: .. code-block:: php firstName, $this->lastName) = explode(' ', $fullName); } } The annotation is defined with one or a list of field names. During hydration, these fields will be checked in order and, for each field present, the annotated method will be invoked with its value as a single argument. Since the ``firstName`` and ``lastName`` fields are mapped, they would then be updated when the Person was persisted back to MongoDB. Unlike lifecycle callbacks, the ``@AlsoLoad`` method annotation does not require the :ref:`haslifecyclecallbacks` class annotation to be present. Moving Fields ------------- Migrating your schema can be a difficult task, but Doctrine provides a few different methods for dealing with it: - **@AlsoLoad** - load values from old fields or transform data through methods - **@NotSaved** - load values into fields without saving them again - **@PostLoad** - execute code after all fields have been loaded - **@PrePersist** - execute code before your document gets saved Imagine you have some address-related fields on a Person document: .. code-block:: php street = $street; $this->city = $city; } } /** @Document @HasLifecycleCallbacks */ class Person { /** @Id */ public $id; /** @Field(type="string") */ public $name; /** @NotSaved */ public $street; /** @NotSaved */ public $city; /** @EmbedOne(targetDocument="Address") */ public $address; /** @PostLoad */ public function postLoad() { if ($this->street !== null || $this->city !== null) { $this->address = new Address($this->street, $this->city); } } } Person's ``street`` and ``city`` fields will be hydrated, but not saved. Once the Person has loaded, the ``postLoad()`` method will be invoked and construct a new Address object, which is mapped and will be persisted. Alternatively, you could defer this migration until the Person is saved: .. code-block:: php street !== null || $this->city !== null) { $this->address = new Address($this->street, $this->city); } } } The :ref:`haslifecyclecallbacks` annotation must be present on the class in which the method is declared for the lifecycle callback to be registered. .. _`$rename`: https://docs.mongodb.com/manual/reference/operator/update/rename/ .. _`Objectify`: https://github.com/objectify/objectify .. _`Objectify schema migration`: https://github.com/objectify/objectify/wiki/SchemaMigration .. _`$or`: https://docs.mongodb.com/manual/reference/operator/query/or/