Advanced field value conversion using custom mapping types

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: reference-basic-mapping-custom-mapping-types).

エンティティを作成するとき、データベースに保存する前にフィールド値を変換する必要がある場合があります。 Doctrine では、これを解決するために Custom MappingTypes を使用できます (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.

これを実現するには、いくつかの方法があります。Typeclass 内で値を変換するか、データベース レベルで値を変換するか、またはその両方を組み合わせます。

This article describes the third way by implementing the MySQL specific column type Point.

この記事では、MySQL 固有の columntype Point を実装する 3 つ目の方法について説明します。

The Point type is part of the Spatial extension 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.

Point タイプは、MySQL の Spatial 拡張機能の一部であり、x 座標と y 座標を使用して座標空間に単一の位置を格納できます。 Point タイプを使用して、経度/緯度のペアを保存し、地理的な場所を表すことができます。

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.

位置の緯度と経度を表す値 objectPoint を保持するフィールド $point を持つ単純なエンティティを作成します。

The entity class:

エンティティ クラス:

<?php

namespace Geo\Entity;

use Geo\ValueObject\Point;

#[Entity]
class Location
{
    #[Column(type: 'point')]
    private Point $point;

    #[Column]
    private string $address;

    public function setPoint(Point $point): void
    {
        $this->point = $point;
    }

    public function getPoint(): Point
    {
        return $this->point;
    }

    public function setAddress(string $address): void
    {
        $this->address = $address;
    }

    public function getAddress(): string
    {
        return $this->address;
    }
}

We use the custom type point in the #[Column] attribute of the $point field. We will create this custom mapping type in the next chapter.

$point フィールドの #[Column] 属性でカスタム タイプ ポイントを使用します。次の章で、このカスタム マッピング タイプを作成します。

The point class:

ポイントクラス:

<?php

namespace Geo\ValueObject;

class Point
{
    public function __construct(
        private float $latitude,
        private float $longitude,
    ) {
    }

    public function getLatitude(): float
    {
        return $this->latitude;
    }

    public function getLongitude(): float
    {
        return $this->longitude;
    }
}

The mapping type

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

次に、ポイント タイプを作成し、必要なすべてのメソッドを実装します。

<?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).

ここでは 2 段階の変換を行います。最初のステップでは、データベースに保存する前に (convertToDatabaseValue メソッドで) Point オブジェクトを文字列表現に変換し、データベースから値をフェッチした後 (convertToPHPValue メソッドで) オブジェクトに戻します。

The format of the string representation format is called Well-known text (WKT). The advantage of this format is, that it is both human readable and parsable by MySQL.

文字列表現形式の形式は Well-known text (WKT) と呼ばれます。この形式の利点は、人間が判読でき、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.

内部的には、MySQL は WKT 形式と同じではないバイナリ形式でジオメトリ値を格納します。そのため、MySQL に WKTrepresentation を内部形式に変換させる必要があります。

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

ここで、convertToPHPValueSQL および convertToDatabaseValueSQL メソッドが機能します。

This methods wrap a sql expression (the WKT representation of the Point) into MySQL functions ST_PointFromText and ST_AsText which convert WKT strings to and from the internal format of MySQL.

このメソッドは、SQL 式 (Point の WKT 表現) を MySQL 関数 ST_PointFromText および ST_AsText にラップします。これらの関数は、WKT 文字列を MySQL の内部形式に変換したり、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!

DQL クエリを使用する場合、convertToPHPValueSQL および convertToDatabaseValueSQL メソッドは、SELECT 句の識別変数とパス式にのみ適用されます。 WHERE 句の式はラップされません!

If you want to use Point values in WHERE clauses, you have to implement a user defined function for PointFromText.

WHERE 句で Point 値を使用する場合は、ユーザー定義関数 forPointFromText を実装する必要があります。

Example usage

<?php

// Bootstrapping stuff...
// $em = new \Doctrine\ORM\EntityManager($connection, $config);

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

Type::addType('point', 'Geo\Types\PointType');
$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 l WHERE l.address = '1600 Amphitheatre Parkway, Mountain View, CA'");
$location = $query->getSingleResult();

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

Table Of Contents

Previous topic

Mysql Enums

Mysql 列挙型

Next topic

Entities in the Session

セッション内のエンティティ

This Page

Fork me on GitHub