This file is indexed.

/usr/share/doc/php-doctrine-orm/html/_sources/cookbook/advanced-field-value-conversion-using-custom-mapping-types.txt is in doctrine-orm-doc 2.4.6-1+deb8u1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
Advanced field value conversion using custom mapping types
==========================================================

.. sectionauthor:: Jan Sorgalla <jsorgalla@googlemail.com>

When creating entities, you sometimes have the need to transform field values
before they are saved to the database. In Doctrine you can use Custom Mapping 
Types to solve this (see: :ref:`reference-basic-mapping-custom-mapping-types`).

There are several ways to achieve this: converting the value inside the Type
class, converting the value on the database-level or a combination of both.

This article describes the third way by implementing the MySQL specific column
type `Point <http://dev.mysql.com/doc/refman/5.5/en/gis-class-point.html>`_.

The ``Point`` type is part of the `Spatial extension <http://dev.mysql.com/doc/refman/5.5/en/spatial-extensions.html>`_
of MySQL and enables you to store a single location in a coordinate space by
using x and y coordinates. You can use the Point type to store a 
longitude/latitude pair to represent a geographic location.

The entity
----------

We create a simple entity with a field ``$point`` which holds a value object
``Point`` representing the latitude and longitude of the position.

The entity class:

.. code-block:: php

    <?php
    
    namespace Geo\Entity;
 
    /**
     * @Entity
     */
    class Location
    {
        /**
         * @Column(type="point")
         *
         * @var \Geo\ValueObject\Point
         */
        private $point;

        /**
         * @Column(type="string")
         *
         * @var string
         */
        private $address;

        /**
         * @param \Geo\ValueObject\Point $point
         */
        public function setPoint(\Geo\ValueObject\Point $point)
        {
            $this->point = $point;
        }

        /**
         * @return \Geo\ValueObject\Point
         */
        public function getPoint()
        {
            return $this->point;
        }

        /**
         * @param string $address
         */
        public function setAddress($address)
        {
            $this->address = $address;
        }

        /**
         * @return string
         */
        public function getAddress()
        {
            return $this->address;
        }
    }

We use the custom type ``point`` in the ``@Column``  docblock annotation of the 
``$point`` field. We will create this custom mapping type in the next chapter.

The point class:

.. code-block:: php

    <?php
    
    namespace Geo\ValueObject;

    class Point
    {

        /**
         * @param float $latitude
         * @param float $longitude
         */
        public function __construct($latitude, $longitude)
        {
            $this->latitude  = $latitude;
            $this->longitude = $longitude;
        }

        /**
         * @return float
         */
        public function getLatitude()
        {
            return $this->latitude;
        }

        /**
         * @return float
         */
        public function getLongitude()
        {
            return $this->longitude;
        }
    }

The mapping type
----------------

Now we're going to create the ``point`` type and implement all required methods.

.. code-block:: php

    <?php

    namespace Geo\Types;

    use Doctrine\DBAL\Types\Type;
    use Doctrine\DBAL\Platforms\AbstractPlatform;

    use Geo\ValueObject\Point;

    class PointType extends Type
    {
        const POINT = 'point';

        public function getName()
        {
            return self::POINT;
        }

        public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
        {
            return 'POINT';
        }

        public function convertToPHPValue($value, AbstractPlatform $platform)
        {
            list($longitude, $latitude) = sscanf($value, 'POINT(%f %f)');

            return new Point($latitude, $longitude);
        }

        public function convertToDatabaseValue($value, AbstractPlatform $platform)
        {
            if ($value instanceof Point) {
                $value = sprintf('POINT(%F %F)', $value->getLongitude(), $value->getLatitude());
            }

            return $value;
        }

        public function canRequireSQLConversion()
        {
            return true;
        }

        public function convertToPHPValueSQL($sqlExpr, AbstractPlatform $platform)
        {
            return sprintf('AsText(%s)', $sqlExpr);
        }

        public function convertToDatabaseValueSQL($sqlExpr, AbstractPlatform $platform)
        {
            return sprintf('PointFromText(%s)', $sqlExpr);
        }
    }

We do a 2-step conversion here. In the first step, we convert the ``Point``
object into a string representation before saving to the database (in the
``convertToDatabaseValue`` method) and back into an object after fetching the
value from the database (in the ``convertToPHPValue`` method).

The format of the string representation format is called `Well-known text (WKT)
<http://en.wikipedia.org/wiki/Well-known_text>`_. The advantage of this format
is, that it is both human readable and parsable by MySQL.

Internally, MySQL stores geometry values in a binary format that is not 
identical to the WKT format. So, we need to let MySQL transform the WKT
representation into its internal format.

This is where the ``convertToPHPValueSQL`` and  ``convertToDatabaseValueSQL``
methods come into play.

This methods wrap a sql expression (the WKT representation of the Point) into
MySQL functions `PointFromText <http://dev.mysql.com/doc/refman/5.5/en/creating-spatial-values.html#function_pointfromtext>`_
and `AsText <http://dev.mysql.com/doc/refman/5.5/en/functions-to-convert-geometries-between-formats.html#function_astext>`_
which convert WKT strings to and from the internal format of MySQL.

.. note::

    When using DQL queries, the ``convertToPHPValueSQL`` and  
    ``convertToDatabaseValueSQL`` methods only apply to identification variables
    and path expressions in SELECT clauses. Expressions in  WHERE clauses are 
    **not** wrapped!

    If you want to use Point values in WHERE clauses, you have to implement a
    :doc:`user defined function <dql-user-defined-functions>` for 
    ``PointFromText``.

Example usage
-------------

.. code-block:: php

    <?php

    // Bootstrapping stuff...
    // $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config);

    // Setup custom mapping type
    use Doctrine\DBAL\Types\Type;

    Type::addType('point', 'Geo\Types\Point');
    $em->getConnection()->getDatabasePlatform()->registerDoctrineTypeMapping('point', 'point');

    // Store a Location object
    use Geo\Entity\Location;
    use Geo\ValueObject\Point;

    $location = new Location();

    $location->setAddress('1600 Amphitheatre Parkway, Mountain View, CA');
    $location->setPoint(new Point(37.4220761, -122.0845187));

    $em->persist($location);
    $em->flush();
    $em->clear();

    // Fetch the Location object
    $query = $em->createQuery("SELECT l FROM Geo\Entity\Location WHERE l.address = '1600 Amphitheatre Parkway, Mountain View, CA'");
    $location = $query->getSingleResult();

    /* @var Geo\ValueObject\Point */
    $point = $location->getPoint();