Contributing to Doctrine Spatial

Welcome to the contribution guide for Doctrine Spatial! This document provides comprehensive instructions and guidelines for those interested in making valuable contributions to the project’s development and enhancement. We encourage participation from all levels of expertise, from beginners to seasoned developers.

Contributing Avenues

The Doctrine Spatial project welcomes contributions in two primary areas:

  1. Documentation Enhancement: Improve the project’s documentation by addressing typos, clarifying instructions, adding helpful examples, or translating documentation into other languages. This helps ensure that users have clear and accessible information to guide them effectively in using Doctrine Spatial.

  2. Codebase Improvement: Contribute to the project’s codebase by fixing bugs, adding new features, optimizing performance, or enhancing existing functionalities. This direct involvement in the code helps maintain and strengthen Doctrine Spatial’s core capabilities.

Documentation

This documentation is done with sphinx. All documentation are stored in the docs directory. To contribute to this documentation (and fix the lot of typo), you could install docker and start the docker service named spatial_doc. It’s included in the repository. It will self-compile the documentation. After you start this service, you can now access the documentation at http://localhost:8100. If you try changing any rst file, after some seconds the browser auto refresh to show the updated documentation. Following the Sphinx documentation site you can now document this project using Sphinx.

  1. Edit files in the docs directory,

  2. Verify that documentation is improved,

  3. Commit your contribution with an explicit message,

  4. Push your commit and create a pull request to the longitude-one/doctrine-spatial project.

Source code

How to create a new function?

It’s pretty easy to create a new function. A few lines code are sufficient.

Where to store your class?

If your function is described in the OGC Standards or in the ISO/IEC 13249-3, the class implementing the function shall be create in the lib/LongitudeOne/Spatial/ORM/Query/AST/Functions/Standard directory.

If your spatial function is not described in the OGC Standards nor in the ISO, your class should be prefixed by Sp (specific). If your class is specific to MySql, you shall create it in the lib/LongitudeOne/Spatial/ORM/Query/AST/Functions/MySql directory. If your class is specific to PostgreSQL, you shall create it in the lib/LongitudeOne/Spatial/ORM/Query/AST/Functions/PostgreSql directory. If your class is not described in the OGC Standards nor in the ISO norm, but exists in MySQL and in PostgreSQL, accepts the same number of arguments and returns the same results (which is rarely the case), then you shall create it in the lib/LongitudeOne/Spatial/ORM/Query/AST/Functions/Common directory.

Which name for your function?

Create a new class. It’s name shall be the same than the function name in camel case prefixed with St or Sp. The standards are alive, they can be updated at any time. Regularly, new spatial function are defined by consortium. So, to avoid that a new standardized function as the same name from an existing function, the St prefix is reserved to already standardized function.

If your function is described in the OGC Standards or in the ISO/IEC 13249-3, the prefix shall be St else your class shall be prefixed with Sp. As example, if you want to create the spatial ST_Z function, your class shall be named StZ in the Standard directory. If you want to create the ST_Polygonize PostgreSql function which is not referenced in the OGC nor in ISO, then you shall name your class SpPolygonize and store them in the PostgreSql directory.

Which method to implements?

Now you know where to create your class, it should extends AbstractSpatialDQLFunction and you have to implement four functions:

  1. getFunctionName() shall return the SQL function name,

  2. getMaxParameter() shall return the maximum number of arguments accepted by the function,

  3. getMinParameter() shall return the minimum number of arguments accepted by the function,

  4. getPlatforms() shall return an array of each platform accepting this function.

As example, if the new spatial function exists in PostgreSQL and in MySQL, getPlatforms() should be like this:

<?php

// ...

/**
 * Get the platforms accepted.
 *
 * @return string[] a non-empty array of accepted platforms
 */
protected function getPlatforms(): array
{
    return ['postgresql', 'mysql'];
}

Do not hesitate to copy and paste the implementing code of an existing spatial function.

If your function is more specific and need to be parse, you can overload the parse method. The PostgreSQL SnapToGrid can be used as example.

All done! Your function is ready to used, but, please, read the next section to implement tests.

Don’t forget to check your code respect our standard of quality:

docker exec spatial-php8 composer check-quality-code

How to test your new function?

Please, create a functional test in the same way. You have a lot of example in the functions test directory.

Setup

Here is an example of setup, each line is commented to help you to understand how to setup your test.

<?php

use LongitudeOne\Spatial\Exception\InvalidValueException;
use LongitudeOne\Spatial\Exception\UnsupportedPlatformException;
use LongitudeOne\Spatial\Tests\Helper\PointHelperTrait;
use LongitudeOne\Spatial\Tests\OrmTestCase;
use Doctrine\DBAL\Exception;
use Doctrine\ORM\Exception\ORMException;

/**
 * Foo DQL functions tests.
 * These tests verify their implementation in doctrine spatial.
 *
 * @author  Alexandre Tranchant <alexandre.tranchant@gmail.com>
 * @license https://alexandre-tranchant.mit-license.org MIT
 *
 * Please preserve the three above annotation.
 *
 * Group is used to exclude some tests on some environment.
 * Internal is to avoid the use of the test outer of this library
 * CoversDefaultClass is to avoid that your test covers other class than your new class
 *
 * @group dql
 *
 * @internal
 * @coversDefaultClass
 */
class SpFooTest extends OrmTestCase
{
    // To help you to create some geometry, I created some Trait.
    // use it to be able to call some methods which will store geometry into your database
    // In this example, we use a trait that will create some points.
    use PointHelperTrait;

    /**
     * Setup the function type test.
     */
    protected function setUp(): void
    {
        //If you create point entity in your test, you shall add the line above or the **next** test will failed
        $this->usesEntity(self::POINT_ENTITY);
        //If the method exists in mysql, You shall test it. Comment this line if function does not exists on MySQL
        $this->supportsPlatform('mysql');
        //If the method exists in postgresql, You shall test it. Comment this line if function does not exists on PostgreSql
        $this->supportsPlatform('postgresql');

        parent::setUp();
    }

    /**
     * Test a DQL containing function to test in the select.
     */
    public function testSelectSpBuffer()
    {
        //The above protected method come from the point helper trait.
        //It creates a point at origin (0 0) and persist it in database
        $pointO = $this->persistPointOrigin();

        //We create a query using your new DQL function SpFoo
        $query = $this->getEntityManager()->createQuery(
            'SELECT p, ST_AsText(SpFoo(p.point, :p) FROM LongitudeOne\Spatial\Tests\Fixtures\PointEntity p'
        );
        //Optionnaly, you can use parameter
        $query->setParameter('p', 'bar', 'string');
        //We retrieve the result
        $result = $query->getResult();

        //Now we test the result
        static::assertCount(1, $result);
        static::assertEquals($pointO, $result[0][0]);
        static::assertSame('POLYGON((-4 -4,4 -4,4 4,-4 4,-4 -4))', $result[0][1]);
    }

Now, open the OrmTestCase.php file] and declare your function in one of this three methods:

  • addStandardFunctions

  • addMySqlFunctions

  • addPostgreSqlFunctions

You can launch the test. This document helps you how to config your dev environment. Please do not forgot to update documentation by adding your function in one of these three tables:

Quality of your code

Quality of code is auto-verified by php-cs-fixer, php code sniffer and php mess detector.

Before a commit, install the quality scripts:

Then, you can check the quality of your code with:

docker exec spatial-php8 quality/php-cs-fixer/vendor/bin/php-cs-fixer fix --config=quality/php-cs-fixer/.php-cs-fixer.php --dry-run --allow-risky=yes
docker exec spatial-php8 quality/php-stan/vendor/bin/phpstan analyse --configuration=quality/php-stan/php-stan.neon lib tests --error-format=table --no-progress --no-interaction --no-ansi --level=9 --memory-limit=256M
docker exec spatial-php8 quality/php-mess-detector/vendor/bin/phpmd lib text quality/php-mess-detector/ruleset.xml
docker exec spatial-php8 quality/php-mess-detector/vendor/bin/phpmd tests text quality/php-mess-detector/test-ruleset.xml
docker exec spatial-php8 quality/php-code-sniffer/vendor/bin/phpcs --standard=quality/php-code-sniffer/phpcs.xml -s

You can launch PHPCS-FIXER to fix errors with:

docker exec spatial-php8 quality/php-cs-fixer/vendor/bin/php-cs-fixer fix --config=quality/php-cs-fixer/.php-cs-fixer.php --allow-risky=yes

Fix all errors, then commit your code.