-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow custom DBAL types autoconfiguration from PHP types #10290
Conversation
327e9c5
to
d3f3455
Compare
I propose this solution:
I have also updated docs under Basic Mapping section. TODO: DoctrineBundle needs to be updated to support config syntax doctrine:
orm:
typed_field_mapping:
App\Objects\CustomObject: App\DBAL\Types\CustomTypeFQCN
App\Objects\CustomObject2: custom_type2_using_name_from_dbal |
d3f3455
to
00ffaa6
Compare
f74e586
to
3e0eb1c
Compare
@greg0ire all feedback is incorporated, please review |
btw, 2.14 release is very close, id like to include this in that release please. Can you add it to the milestone @derrabus please? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So far so good, I have one improvement idea, but generally this is great, thank you for working on it
@beberlei I took your suggestion and extended the implementation to allow overriding of the default types as well. Added test for this as well as documentation |
25fbeca
to
ad3ce3d
Compare
Previously, only a subset of native PHP types (DateTime, array, bool, float, int,...) did not need an explicit type specification inside the column declaration. This commit allows to specify mapping between PHP types and DBAL types for any explicitly typed PHP field. A new Configuration option has been added for this purpose and the configured mapping is inserted into ClassMetadata object during its construction.
ad3ce3d
to
56cded6
Compare
I have added better psalm type for the |
I got one more idea, for another config option, but it should be part of this commit I think: doctrine:
orm:
typed_field_mapping:
App\Objects\CustomObject: App\DBAL\Types\CustomTypeFQCN
App\Objects\CustomObject2: custom_type2_using_name_from_dbal
automatic_enum_field_mapping: true #default false If this is set, it would consider #[Column]
public MyEnum $enumField; as #[Column(type: 'string', enumType: MyEnum::class)]
public MyEnum $enumField; (of course WDYT? |
well actually this is already implemented, and it is always used, i.e. #[Column]
public MyEnum $enumField; will work just fine. However I got another idea in the process. Imagine you have a custom DBAL type for enums. All your enums in your code are supposed to use this DBAL type. With the feature as is now in this PR, the only way is to list all your enums like doctrine:
orm:
typed_field_mapping:
App\Enum\Enum1: App\DBAL\Types\CustomEnumType
App\Enum\Enum2: App\DBAL\Types\CustomEnumType
# ... Proposal:Lets move the class DefaultTypedFieldMapper implements TypedFieldMapper where interface TypedFieldMapper
{
/**
* Validates & completes the given field mapping based on typed property.
*
* @param array{fieldName: string, type?: mixed} $mapping The field mapping to validate & complete.
*
* @return array{fieldName: string, enumType?: string, type?: mixed} The updated mapping.
*/
public function validateAndCompleteTypedFieldMapping(array $mapping, ReflectionProperty $field): array;
/**
* Registers a custom PHP type => DBAL Type mapping for typed Entity fields.
* With this configuration option used there is no need to specify the Column::type
* for given explicitly typed field.
*
* @psalm-param class-string|ScalarName $phpType the FQCN of PHP type to be mapped
* @psalm-param class-string<Type>|string $dbalType either FQCN of DBAL Type or its name
*/
public function addTypedFieldMapping(string $phpType, string $dbalType): void;
} we would then allow this config as well: doctrine:
orm:
typed_field_mapper: App\ORM\CustomTypedFieldMapper
typed_field_mapping:
App\Objects\CustomObject: App\DBAL\Types\CustomTypeFQCN
App\Objects\CustomObject2: custom_type2_using_name_from_dbal such custom typed field mapper would be able to check e.g. for inheritance using instanceof, compare type names or whatever flexibility is desired. Notice that we can still add custom typed field mapping in the config, this would be passed to the The |
this requires the TypedFieldMapper to be a mutable object, preventing sharing the same instance between multiple configuration objects (as that would merge the mappings of each configuration). This looks like a bad design to me. I would rather keep this simple mapping as a constructor argument of the DefaultTypedFieldMapper instead (and custom mappers are free to implement their config as they want) |
The main reason I wanted to implement is this way is that I still want to allow config like this: doctrine:
orm:
typed_field_mapping:
App\Objects\CustomObject: App\DBAL\Types\CustomTypeFQCN
App\Objects\CustomObject2: custom_type2_using_name_from_dbal where the default type mapper is used, but a list is provided for some additional mappings. I dont understand why mutability of the TypedFieldMapper is important, one instance will be created and passed into |
@stof ok, I think I get your point now, so how about this: <?php
declare(strict_types=1);
namespace ORM\Mapping;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use ReflectionProperty;
/** @psalm-import-type ScalarName from ClassMetadataInfo */
interface TypedFieldMapper
{
/** @param array<class-string|ScalarName, class-string<Type>|string> $typedFieldMappings */
public function __construct(array $typedFieldMappings = []);
/**
* Validates & completes the given field mapping based on typed property.
*
* @param array{fieldName: string, type?: mixed} $mapping The field mapping to validate & complete.
*
* @return array{fieldName: string, enumType?: string, type?: mixed} The updated mapping.
*/
public function validateAndCompleteTypedFieldMapping(array $mapping, ReflectionProperty $field): array;
} |
@michnovka I would not enforce the signature of the constructor. Custom mapper will legitimately have different configuration needs. |
@michnovka don't only think about how things look like in DoctrineBundle. The doctrine/orm project is usable in other contexts as well. So the design of the API must make sense on its own. |
@stof ok, so can you please suggest what you meant with the mutability? I dont see how you want to share the instance between multiple configurations AND at the same time have different behavior. Nothing prevents you to make more instances of your CustomTypedFieldMapper and use them in different configuration scenarios with different default typedFieldMapping settings. So if you see some issue in this, can you please give me a concrete example so that I can understand the problem and think of a solution? |
@stof the problem is, that I want to keep an option to only specify the additional If I pass it in constructor, the object CAN be immutable (but no way to force this with PHP, somebody can make their own If I pass it in some additonal method, I can do this: /**
* @param array<class-string|ScalarName, class-string<Type>|string> $typedFieldMappings
*/
public function setTypedFieldMappings(array $typedFieldMappings = []): self; Which would return a new instance always, thus imitate immutability (like But I think we should not attempt to force immutability. For all we know, mutability can be desired under some scenario. If somebody wants to have 2 different configs for one Like I said, we cannot enforce that any implementation of For that and the fact that some scenario might benefit from mutability, I dont want to satisfy this requirement. Thoughts? |
@michnovka to me, this shows a need for a ChainTypedFieldMapper allowing to combine multiple mappers to use them together.
I don't want to enforce immutability. I want to avoid requiring mutability. that's not the same. |
@stof and what about this: <?php
declare(strict_types=1);
namespace ORM\Mapping;
use ReflectionProperty;
interface TypedFieldMapper
{
/**
* Validates & completes the given field mapping based on typed property.
*
* @param array{fieldName: string, type?: mixed} $mapping The field mapping to validate & complete.
*
* @return array{fieldName: string, enumType?: string, type?: mixed} The updated mapping.
*/
public function validateAndCompleteTypedFieldMapping(array $mapping, ReflectionProperty $field): array;
} this does not require mutability then the yaml config can look like doctrine:
orm:
typed_field_mapper: 'default'
arguments:
$typedFieldMappings:
- App\Objects\CustomObject: App\DBAL\Types\CustomTypeFQCN
- App\Objects\CustomObject2: custom_type2_using_name_from_dbal or doctrine:
orm:
typed_field_mapper: App\ORM\CustomTypedFieldMapper
arguments:
$typedFieldMappings:
- App\Objects\CustomObject: App\DBAL\Types\CustomTypeFQCN
- App\Objects\CustomObject2: custom_type2_using_name_from_dbal
$someSecondArgumentOfConstructor: true or doctrine:
orm:
typed_field_mapper: App\ORM\CustomTypedFieldMapper2
arguments:
$totallyDifferentConstructorArgument: false The doctrine-bundle syntax is not as nice as originally proposed, but it does allow for greater flexibility. Not all And I can also make a class like class ChainTypedFieldMapper implements TypedFieldMapper
{
public function __construct(TypedFieldMapper ...$typedFieldMappers){
// ... store them in some collection and process them all accordingly with validateAndCompleteTypedFieldMapping
}
// ... which could have this support in doctrine bundle: doctrine:
orm:
typed_field_mapper: 'chain'
arguments:
$typedFieldMappers:
- 'default':
arguments:
$typedFieldMappings:
- App\Objects\CustomObject: App\DBAL\Types\CustomTypeFQCN
- App\Objects\CustomObject2: custom_type2_using_name_from_dbal
- App\ORM\CustomTypedFieldMapper2:
arguments:
$totallyDifferentConstructorArgument: false |
you are over-thinking the DoctrineBundle configuration syntax. A custom mapper will be defined as a service seperately and will only be referenced in the config (or registered through a tag applied on it depending on the final solution). But anyway, that's a discussion to have in DoctrineBundle after we know what the ORM API is. |
@stof regardless of the doctrinebundle config, the other things I proposed in the above comment address your feedback completely, so do you agree with that proposal? |
I can no longer vote to merge this PR, since a far superior version has implemented here - #10313. We will continue discussion there |
Work in progress to implement #9561
I wanted to use this syntax in configuration:
The problem I am facing is that
ClassMetadataInfo
does not seem to have access to theConfiguration
object. I wonder what would be the proper strategy here?I've explored some ideas, like adding another param to the
ClassMetadataInfo::mapField($mapping)
function, but even its callers do not have access to theConfiguration
object.@beberlei @derrabus @greg0ire please, any ideas?