Doctrine\DBAL\Types\Type::__toString str_replace() bug #5082

Open
opened 2026-01-22 14:57:55 +01:00 by admin · 3 comments
Owner

Originally created by @d42ohpaz on GitHub (Apr 4, 2016).

When a database type is named using the word 'Type', it will get removed and cause the following error:

Error: Class 'MyAppBundle\Type\TypeAccount' not found

/Users/dohpaz42/Development/MyApp/src/MyAppBundle/Type/TypeAccountType.php:18
/Users/dohpaz42/Development/MyApp/vendor/doctrine/orm/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php:128
/Users/dohpaz42/Development/MyApp/vendor/doctrine/orm/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php:69
/Users/dohpaz42/Development/MyApp/vendor/doctrine/orm/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php:147
/Users/dohpaz42/Development/MyApp/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php:888
/Users/dohpaz42/Development/MyApp/vendor/doctrine/orm/lib/Doctrine/ORM/EntityRepository.php:181
/Users/dohpaz42/Development/MyApp/vendor/doctrine/orm/lib/Doctrine/ORM/EntityRepository.php:164
/Users/dohpaz42/Development/MyApp/tests/MyAppBundle/Entity/AccountTest.php:29

The following defines my type in code. I've found that if I do not return an instance of the type from convertToPHPValue I do not get the error.

Doctrine config

# Doctrine Configuration
doctrine:
    dbal:
        driver:   pdo_pgsql
        host:     "%database_host%"
        port:     "%database_port%"
        dbname:   "%database_name%"
        user:     "%database_user%"
        password: "%database_password%"
        charset:  UTF8

        types:
            currency:       MyAppBundle\Type\CurrencyType
            type_tx:        MyAppBundle\Type\TypeTransactionType
            acct_equation:  MyAppBundle\Type\AccountEquationType
            type_acct:      MyAppBundle\Type\TypeAccountType
            datetimetz:     Doctrine\DBAL\Types\VarDateTimeType

MyAppBundle\Type\TypeAccountType

<?php
namespace MyAppBundle\Type;

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

class TypeAccountType extends Type
{
    const TYPE_ACCOUNT = 'type_acct';

    public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
    {
        return $this::TYPE_ACCOUNT;
    }

    public function convertToPHPValue($value, AbstractPlatform $platform)
    {
        return new TypeAccount($value);
    }

    public function convertToDatabaseValue($value, AbstractPlatform $platform)
    {
        return $value->toTypeAccount();
    }

    public function getName()
    {
        return $this::TYPE_ACCOUNT;
    }
}

I've tracked the error down to Doctrine\DBAL\Types\Type::__toString() in that it blindly removes any instance of the word Type from the class name, when in fact the intent is to remove the last instance of the word Type. Since my class is TypeAccountType, it is being turned into Account, which does not exist and causes the error to be thrown.

Doctrine\DBAL\Types\Type

    /**
     * @return string
     */
    public function __toString()
    {
        $e = explode('\\', get_class($this));

        return str_replace('Type', '', end($e));
    }

I am willing to make a unit test and a PR, but wanted to document and get feedback first.

Originally created by @d42ohpaz on GitHub (Apr 4, 2016). When a database type is named using the word 'Type', it will get removed and cause the following error: ``` Error: Class 'MyAppBundle\Type\TypeAccount' not found /Users/dohpaz42/Development/MyApp/src/MyAppBundle/Type/TypeAccountType.php:18 /Users/dohpaz42/Development/MyApp/vendor/doctrine/orm/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php:128 /Users/dohpaz42/Development/MyApp/vendor/doctrine/orm/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php:69 /Users/dohpaz42/Development/MyApp/vendor/doctrine/orm/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php:147 /Users/dohpaz42/Development/MyApp/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php:888 /Users/dohpaz42/Development/MyApp/vendor/doctrine/orm/lib/Doctrine/ORM/EntityRepository.php:181 /Users/dohpaz42/Development/MyApp/vendor/doctrine/orm/lib/Doctrine/ORM/EntityRepository.php:164 /Users/dohpaz42/Development/MyApp/tests/MyAppBundle/Entity/AccountTest.php:29 ``` The following defines my type in code. I've found that if I do not return an instance of the type from `convertToPHPValue` I do not get the error. ### Doctrine config ``` # Doctrine Configuration doctrine: dbal: driver: pdo_pgsql host: "%database_host%" port: "%database_port%" dbname: "%database_name%" user: "%database_user%" password: "%database_password%" charset: UTF8 types: currency: MyAppBundle\Type\CurrencyType type_tx: MyAppBundle\Type\TypeTransactionType acct_equation: MyAppBundle\Type\AccountEquationType type_acct: MyAppBundle\Type\TypeAccountType datetimetz: Doctrine\DBAL\Types\VarDateTimeType ``` ### MyAppBundle\Type\TypeAccountType ``` <?php namespace MyAppBundle\Type; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Platforms\AbstractPlatform; class TypeAccountType extends Type { const TYPE_ACCOUNT = 'type_acct'; public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { return $this::TYPE_ACCOUNT; } public function convertToPHPValue($value, AbstractPlatform $platform) { return new TypeAccount($value); } public function convertToDatabaseValue($value, AbstractPlatform $platform) { return $value->toTypeAccount(); } public function getName() { return $this::TYPE_ACCOUNT; } } ``` I've tracked the error down to Doctrine\DBAL\Types\Type::__toString() in that it blindly removes any instance of the word `Type` from the class name, when in fact the intent is to remove the last instance of the word `Type`. Since my class is `TypeAccountType`, it is being turned into `Account`, which does not exist and causes the error to be thrown. ### Doctrine\DBAL\Types\Type ``` /** * @return string */ public function __toString() { $e = explode('\\', get_class($this)); return str_replace('Type', '', end($e)); } ``` I am willing to make a unit test and a PR, but wanted to document and get feedback first.
Author
Owner

@DHager commented on GitHub (Apr 6, 2016):

Makes sense, but it might be better in doctrine/dbal if there isn't any ORM code involved.

I'm trying to think whether there's any way somebody might have worked-around the problem that would result in a backwards-compatibility break if it were to be fixed.

@DHager commented on GitHub (Apr 6, 2016): Makes sense, but it might be better in [doctrine/dbal](https://github.com/doctrine/dbal/issues) if there isn't any ORM code involved. I'm trying to think whether there's any way somebody might have worked-around the problem that would result in a backwards-compatibility break if it were to be fixed.
Author
Owner

@d42ohpaz commented on GitHub (Apr 6, 2016):

The way I see it, there are two plausible workarounds:

  1. Rename your types so they do not include the word 'type'
  2. Overload the __toString method and implement your own solution

In either case, no BC would occur.

As for where to put this, this is defined in the Types\Type, why wouldn't it be fixed in the same place? Forgive my ignorance, as I'm new to Symfony and its methodologies.

Edit: Okay, I realize now I didn't specifically point out the code that needs to be fixed. My apologies.

The current implementation of Doctrine\DBAL\Types\Type::__toString uses explode() and end(), in combination with str_replace() to remove the word Type from the class name:

    /**
     * @return string
     */
    public function __toString()
    {
        $e = explode('\\', get_class($this));

        return str_replace('Type', '', end($e));
    }

Because of the use of str_replace() it doesn't discriminate between the end of the class name and what may show up in the middle. For example, if I use TypeAccountType, the __toString method would return Account because it removed both instances of the word Type. My suggestion, and what I plan on making a PR for, would be to use something similar to the following:

    /**
     * @return string
     */
    public function __toString()
    {
        $class = get_class($this);

        return substr($class, strrpos($class, '\\'), -4);
    }

This effectively gives the same result as expected, but would leave any other instance of the word Type as-is, so that in my example, TypeAccountType would be TypeAccount. Any other combination would be fine too, such as TypeTypeType would be TypeType (the current code would return an empty string).

Does this clear things up?

@d42ohpaz commented on GitHub (Apr 6, 2016): The way I see it, there are two plausible workarounds: 1. Rename your types so they do not include the word 'type' 2. Overload the __toString method and implement your own solution In either case, no BC would occur. As for where to put this, this is defined in the Types\Type, why wouldn't it be fixed in the same place? Forgive my ignorance, as I'm new to Symfony and its methodologies. Edit: Okay, I realize now I didn't specifically point out the code that needs to be fixed. My apologies. The current implementation of `Doctrine\DBAL\Types\Type::__toString` uses `explode()` and `end()`, in combination with `str_replace()` to remove the word `Type` from the class name: ``` /** * @return string */ public function __toString() { $e = explode('\\', get_class($this)); return str_replace('Type', '', end($e)); } ``` Because of the use of `str_replace()` it doesn't discriminate between the end of the class name and what may show up in the middle. For example, if I use TypeAccountType, the `__toString` method would return `Account` because it removed _both_ instances of the word `Type`. My suggestion, and what I plan on making a PR for, would be to use something similar to the following: ``` /** * @return string */ public function __toString() { $class = get_class($this); return substr($class, strrpos($class, '\\'), -4); } ``` This effectively gives the same result as expected, but would leave any other instance of the word `Type` as-is, so that in my example, TypeAccountType would be TypeAccount. Any other combination would be fine too, such as TypeTypeType would be TypeType (the current code would return an empty string). Does this clear things up?
Author
Owner

@d42ohpaz commented on GitHub (Apr 6, 2016):

It's not specifically related to this ticket, but I also wanted to point out that using substr() and strrpos() would also net a 2x performance boost over the use of explode() and end().

@d42ohpaz commented on GitHub (Apr 6, 2016): It's not specifically related to this ticket, but I also wanted to point out that using `substr()` and `strrpos()` would also net a 2x performance boost over the use of `explode()` and `end()`.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#5082