From da77700ff602bd79f3bad1ffaead76034eeaa5d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Nguyen?= Date: Wed, 14 Mar 2018 22:25:54 +0100 Subject: [PATCH 1/5] add support for Blob and binary types in bean generation fixes #69 --- src/Utils/ScalarBeanPropertyDescriptor.php | 15 +++++++++++++++ src/Utils/TDBMDaoGenerator.php | 16 +++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/Utils/ScalarBeanPropertyDescriptor.php b/src/Utils/ScalarBeanPropertyDescriptor.php index af77264f..1944fb36 100644 --- a/src/Utils/ScalarBeanPropertyDescriptor.php +++ b/src/Utils/ScalarBeanPropertyDescriptor.php @@ -86,6 +86,17 @@ public function getPhpType(): string return TDBMDaoGenerator::dbalTypeToPhpType($type); } + /** + * Returns the PHP type for the property (it can be a scalar like int, bool, or class names, like \DateTimeInterface, App\Bean\User....) + * + * @return string + */ + public function canBeSerialized(): string + { + $type = $this->column->getType(); + return TDBMDaoGenerator::isSerializableType($type); + } + /** * Returns true if the property is compulsory (and therefore should be fetched in the constructor). * @@ -274,6 +285,10 @@ public function getJsonSerializeCode() { $normalizedType = $this->getPhpType(); +// if (!$this->canBeSerialized()){ +// return ''; +// } + if ($normalizedType == '\\DateTimeImmutable') { return ' $array['.var_export($this->namingStrategy->getJsonProperty($this), true).'] = ($this->'.$this->getGetterName().'() === null) ? null : $this->'.$this->getGetterName()."()->format('c');\n"; } else { diff --git a/src/Utils/TDBMDaoGenerator.php b/src/Utils/TDBMDaoGenerator.php index 0d731519..dcb06f4f 100644 --- a/src/Utils/TDBMDaoGenerator.php +++ b/src/Utils/TDBMDaoGenerator.php @@ -640,7 +640,7 @@ private function dumpFile(string $fileName, string $content) : void * * @return string The PHP type */ - public static function dbalTypeToPhpType(Type $type) + public static function dbalTypeToPhpType(Type $type) : string { $map = [ Type::TARRAY => 'array', @@ -659,12 +659,22 @@ public static function dbalTypeToPhpType(Type $type) Type::SMALLINT => 'int', Type::STRING => 'string', Type::TEXT => 'string', - Type::BINARY => 'string', - Type::BLOB => 'string', + Type::BINARY => 'resource', + Type::BLOB => 'resource', Type::FLOAT => 'float', Type::GUID => 'string', ]; return isset($map[$type->getName()]) ? $map[$type->getName()] : $type->getName(); } + + public static function isSerializableType(Type $type) : bool + { + $unserialisableTypes = [ + Type::BLOB, + Type::BINARY + ]; + + return \in_array($type->getName(), $unserialisableTypes, true) === false; + } } From b41065e69cdf51cfb5d05c98641340edd922300a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Nguyen?= Date: Wed, 14 Mar 2018 22:58:43 +0100 Subject: [PATCH 2/5] fix commented code by mistake --- src/Utils/ScalarBeanPropertyDescriptor.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Utils/ScalarBeanPropertyDescriptor.php b/src/Utils/ScalarBeanPropertyDescriptor.php index 1944fb36..e73af3c4 100644 --- a/src/Utils/ScalarBeanPropertyDescriptor.php +++ b/src/Utils/ScalarBeanPropertyDescriptor.php @@ -285,9 +285,9 @@ public function getJsonSerializeCode() { $normalizedType = $this->getPhpType(); -// if (!$this->canBeSerialized()){ -// return ''; -// } + if (!$this->canBeSerialized()){ + return ''; + } if ($normalizedType == '\\DateTimeImmutable') { return ' $array['.var_export($this->namingStrategy->getJsonProperty($this), true).'] = ($this->'.$this->getGetterName().'() === null) ? null : $this->'.$this->getGetterName()."()->format('c');\n"; From 74ad6173a7e05e519857e0268a58ee3284d8bf57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Nguyen?= Date: Wed, 14 Mar 2018 23:14:28 +0100 Subject: [PATCH 3/5] resource is not a valid scalar type --- src/Utils/ScalarBeanPropertyDescriptor.php | 39 ++++++++++++++-------- src/Utils/TDBMDaoGenerator.php | 19 +++++++++++ 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/src/Utils/ScalarBeanPropertyDescriptor.php b/src/Utils/ScalarBeanPropertyDescriptor.php index e73af3c4..16b9792a 100644 --- a/src/Utils/ScalarBeanPropertyDescriptor.php +++ b/src/Utils/ScalarBeanPropertyDescriptor.php @@ -86,17 +86,6 @@ public function getPhpType(): string return TDBMDaoGenerator::dbalTypeToPhpType($type); } - /** - * Returns the PHP type for the property (it can be a scalar like int, bool, or class names, like \DateTimeInterface, App\Bean\User....) - * - * @return string - */ - public function canBeSerialized(): string - { - $type = $this->column->getType(); - return TDBMDaoGenerator::isSerializableType($type); - } - /** * Returns true if the property is compulsory (and therefore should be fetched in the constructor). * @@ -234,7 +223,7 @@ public function getGetterSetterCode() * * @return %s */ - public function %s() : %s%s + public function %s() %s %s%s { return $this->get(%s, %s); } @@ -257,8 +246,9 @@ public function %s(%s%s $%s) : void $this->column->getName(), $normalizedType.($isNullable ? '|null' : ''), $columnGetterName, - ($isNullable ? '?' : ''), - $normalizedType, + ($this->isValidScalarType() ? ':' : ''), + ($isNullable && $this->isValidScalarType() ? '?' : ''), + ($this->isValidScalarType() ? $normalizedType: ''), var_export($this->column->getName(), true), var_export($this->table->getName(), true), // Setter @@ -318,4 +308,25 @@ public function getCloneRule(): ?string } return null; } + + /** + * tells is this type is suitable for Json Serialization + * + * @return string + */ + public function canBeSerialized() : string + { + $type = $this->column->getType(); + return TDBMDaoGenerator::isSerializableType($type); + } + + /** + * Tells is this type is a valid scalar type (resource isn't for example) + * @return bool + */ + public function isValidScalarType() : bool + { + $type = $this->getPhpType(); + return TDBMDaoGenerator::isValidScalarType($type); + } } diff --git a/src/Utils/TDBMDaoGenerator.php b/src/Utils/TDBMDaoGenerator.php index dcb06f4f..70ec30d1 100644 --- a/src/Utils/TDBMDaoGenerator.php +++ b/src/Utils/TDBMDaoGenerator.php @@ -668,6 +668,11 @@ public static function dbalTypeToPhpType(Type $type) : string return isset($map[$type->getName()]) ? $map[$type->getName()] : $type->getName(); } + /** + * Tells if a given column type can be Json Serialized (Blob and Binary are not for instance) + * @param Type $type + * @return bool + */ public static function isSerializableType(Type $type) : bool { $unserialisableTypes = [ @@ -677,4 +682,18 @@ public static function isSerializableType(Type $type) : bool return \in_array($type->getName(), $unserialisableTypes, true) === false; } + + /** + * Tells if a given php type is a valid scalar type (resource is not) + * @param string $type + * @return bool + */ + public static function isValidScalarType(string $type) : bool + { + $invalidScalarTypes = [ + 'resource' + ]; + + return \in_array($type, $invalidScalarTypes, true) === false; + } } From ba64ba44374f02287ca2967a41da97a667bc7578 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Nguyen?= Date: Wed, 14 Mar 2018 23:43:13 +0100 Subject: [PATCH 4/5] must be set as string but returns resource... this is driving me crazy \-.-/ --- src/Utils/ScalarBeanPropertyDescriptor.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Utils/ScalarBeanPropertyDescriptor.php b/src/Utils/ScalarBeanPropertyDescriptor.php index 16b9792a..d97dc04e 100644 --- a/src/Utils/ScalarBeanPropertyDescriptor.php +++ b/src/Utils/ScalarBeanPropertyDescriptor.php @@ -257,7 +257,7 @@ public function %s(%s%s $%s) : void $this->column->getName(), $columnSetterName, $this->column->getNotnull() ? '' : '?', - $normalizedType, + (!$this->forceSetAsString() ? $normalizedType : "string"), //$castTo, $this->column->getName(), var_export($this->column->getName(), true), @@ -329,4 +329,9 @@ public function isValidScalarType() : bool $type = $this->getPhpType(); return TDBMDaoGenerator::isValidScalarType($type); } + + public function forceSetAsString(){ + $type = $this->getPhpType(); + return $type === "resource"; + } } From 66efaca9f68a0543010cc3761634898a923a45e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 15 Mar 2018 15:49:23 +0100 Subject: [PATCH 5/5] Finalizing proper blob support --- src/TDBMInvalidArgumentException.php | 7 ++ src/Utils/AbstractBeanPropertyDescriptor.php | 7 ++ src/Utils/BeanDescriptor.php | 2 +- src/Utils/ObjectBeanPropertyDescriptor.php | 10 +++ src/Utils/ScalarBeanPropertyDescriptor.php | 42 +++++++----- src/Utils/TDBMDaoGenerator.php | 14 ---- tests/TDBMAbstractServiceTest.php | 4 ++ tests/TDBMDaoGeneratorTest.php | 70 +++++++++++++++++--- 8 files changed, 115 insertions(+), 41 deletions(-) diff --git a/src/TDBMInvalidArgumentException.php b/src/TDBMInvalidArgumentException.php index 5e32d6d0..70574a4f 100644 --- a/src/TDBMInvalidArgumentException.php +++ b/src/TDBMInvalidArgumentException.php @@ -4,4 +4,11 @@ class TDBMInvalidArgumentException extends \InvalidArgumentException { + /** + * @param mixed $value + */ + public static function badType(string $expectedType, $value, string $location): self + { + return new self("Invalid argument passed to '$location'. Expecting a $expectedType. Got a ".gettype($value).'.'); + } } diff --git a/src/Utils/AbstractBeanPropertyDescriptor.php b/src/Utils/AbstractBeanPropertyDescriptor.php index d0f533d4..5222be9c 100644 --- a/src/Utils/AbstractBeanPropertyDescriptor.php +++ b/src/Utils/AbstractBeanPropertyDescriptor.php @@ -156,4 +156,11 @@ public function isAlternativeName(): bool * @return null|string */ abstract public function getCloneRule(): ?string; + + /** + * Tells if this property is a type-hintable in PHP (resource isn't for example) + * + * @return bool + */ + abstract public function isTypeHintable() : bool; } diff --git a/src/Utils/BeanDescriptor.php b/src/Utils/BeanDescriptor.php index 02d5233f..05b0456e 100644 --- a/src/Utils/BeanDescriptor.php +++ b/src/Utils/BeanDescriptor.php @@ -283,7 +283,7 @@ public function __construct(%s) $parentConstructorArguments = []; foreach ($constructorProperties as $property) { - $arguments[] = $property->getPhpType().' '.$property->getVariableName(); + $arguments[] = ($property->isTypeHintable()?($property->getPhpType().' '):'').$property->getVariableName(); $paramAnnotations[] = $property->getParamAnnotation(); if ($property->getTable()->getName() === $this->table->getName()) { $assigns[] = $property->getConstructorAssignCode()."\n"; diff --git a/src/Utils/ObjectBeanPropertyDescriptor.php b/src/Utils/ObjectBeanPropertyDescriptor.php index 1bf568c2..c45fc560 100644 --- a/src/Utils/ObjectBeanPropertyDescriptor.php +++ b/src/Utils/ObjectBeanPropertyDescriptor.php @@ -198,4 +198,14 @@ public function getCloneRule(): ?string { return null; } + + /** + * Tells if this property is a type-hintable in PHP (resource isn't for example) + * + * @return bool + */ + public function isTypeHintable() : bool + { + return true; + } } diff --git a/src/Utils/ScalarBeanPropertyDescriptor.php b/src/Utils/ScalarBeanPropertyDescriptor.php index d97dc04e..5f603436 100644 --- a/src/Utils/ScalarBeanPropertyDescriptor.php +++ b/src/Utils/ScalarBeanPropertyDescriptor.php @@ -218,12 +218,23 @@ public function getGetterSetterCode() // A column type can be forced if it is not nullable and not auto-incrementable (for auto-increment columns, we can get "null" as long as the bean is not saved). $isNullable = !$this->column->getNotnull() || $this->isAutoincrement(); + $resourceTypeCheck = ''; + if ($normalizedType === 'resource') { + $resourceTypeCheck .= <<column->getName(), $this->column->getName()); + } + $getterAndSetterCode = ' /** * The getter for the "%s" column. * * @return %s */ - public function %s() %s %s%s + public function %s()%s%s%s { return $this->get(%s, %s); } @@ -233,8 +244,8 @@ public function %s() %s %s%s * * @param %s $%s */ - public function %s(%s%s $%s) : void - { + public function %s(%s%s$%s) : void + {%s $this->set(%s, $%s, %s); } @@ -246,9 +257,9 @@ public function %s(%s%s $%s) : void $this->column->getName(), $normalizedType.($isNullable ? '|null' : ''), $columnGetterName, - ($this->isValidScalarType() ? ':' : ''), - ($isNullable && $this->isValidScalarType() ? '?' : ''), - ($this->isValidScalarType() ? $normalizedType: ''), + ($this->isTypeHintable() ? ' : ' : ''), + ($isNullable && $this->isTypeHintable() ? '?' : ''), + ($this->isTypeHintable() ? $normalizedType: ''), var_export($this->column->getName(), true), var_export($this->table->getName(), true), // Setter @@ -256,10 +267,11 @@ public function %s(%s%s $%s) : void $normalizedType.($isNullable ? '|null' : ''), $this->column->getName(), $columnSetterName, - $this->column->getNotnull() ? '' : '?', - (!$this->forceSetAsString() ? $normalizedType : "string"), + ($this->column->getNotnull() || !$this->isTypeHintable()) ? '' : '?', + $this->isTypeHintable() ? $normalizedType . ' ' : '', //$castTo, $this->column->getName(), + $resourceTypeCheck, var_export($this->column->getName(), true), $this->column->getName(), var_export($this->table->getName(), true) @@ -321,17 +333,17 @@ public function canBeSerialized() : string } /** - * Tells is this type is a valid scalar type (resource isn't for example) + * Tells if this property is a type-hintable in PHP (resource isn't for example) + * * @return bool */ - public function isValidScalarType() : bool + public function isTypeHintable() : bool { $type = $this->getPhpType(); - return TDBMDaoGenerator::isValidScalarType($type); - } + $invalidScalarTypes = [ + 'resource' + ]; - public function forceSetAsString(){ - $type = $this->getPhpType(); - return $type === "resource"; + return \in_array($type, $invalidScalarTypes, true) === false; } } diff --git a/src/Utils/TDBMDaoGenerator.php b/src/Utils/TDBMDaoGenerator.php index 70ec30d1..701bb4d4 100644 --- a/src/Utils/TDBMDaoGenerator.php +++ b/src/Utils/TDBMDaoGenerator.php @@ -682,18 +682,4 @@ public static function isSerializableType(Type $type) : bool return \in_array($type->getName(), $unserialisableTypes, true) === false; } - - /** - * Tells if a given php type is a valid scalar type (resource is not) - * @param string $type - * @return bool - */ - public static function isValidScalarType(string $type) : bool - { - $invalidScalarTypes = [ - 'resource' - ]; - - return \in_array($type, $invalidScalarTypes, true) === false; - } } diff --git a/tests/TDBMAbstractServiceTest.php b/tests/TDBMAbstractServiceTest.php index 2ec6287f..d7ce7f53 100644 --- a/tests/TDBMAbstractServiceTest.php +++ b/tests/TDBMAbstractServiceTest.php @@ -317,6 +317,10 @@ private static function initSchema(Connection $connection): void ->column('id')->string(36)->primaryKey()->comment('@UUID v4') ->column('content')->string(255); + $db->table('files') + ->column('id')->integer()->primaryKey()->autoIncrement()->comment('@Autoincrement') + ->column('file')->blob(); + $toSchema->getTable('users') ->addUniqueIndex([$connection->quoteIdentifier('login')], 'users_login_idx') ->addIndex([$connection->quoteIdentifier('status'), $connection->quoteIdentifier('country_id')], 'users_status_country_idx'); diff --git a/tests/TDBMDaoGeneratorTest.php b/tests/TDBMDaoGeneratorTest.php index 39843e75..fd65ff09 100644 --- a/tests/TDBMDaoGeneratorTest.php +++ b/tests/TDBMDaoGeneratorTest.php @@ -39,6 +39,7 @@ use TheCodingMachine\TDBM\Test\Dao\Bean\CategoryBean; use TheCodingMachine\TDBM\Test\Dao\Bean\CountryBean; use TheCodingMachine\TDBM\Test\Dao\Bean\DogBean; +use TheCodingMachine\TDBM\Test\Dao\Bean\FileBean; use TheCodingMachine\TDBM\Test\Dao\Bean\Generated\UserBaseBean; use TheCodingMachine\TDBM\Test\Dao\Bean\PersonBean; use TheCodingMachine\TDBM\Test\Dao\Bean\RefNoPrimKeyBean; @@ -50,6 +51,7 @@ use TheCodingMachine\TDBM\Test\Dao\ContactDao; use TheCodingMachine\TDBM\Test\Dao\CountryDao; use TheCodingMachine\TDBM\Test\Dao\DogDao; +use TheCodingMachine\TDBM\Test\Dao\FileDao; use TheCodingMachine\TDBM\Test\Dao\Generated\UserBaseDao; use TheCodingMachine\TDBM\Test\Dao\RefNoPrimKeyDao; use TheCodingMachine\TDBM\Test\Dao\RoleDao; @@ -72,20 +74,20 @@ protected function setUp() $schemaAnalyzer = new SchemaAnalyzer($schemaManager); $tdbmSchemaAnalyzer = new TDBMSchemaAnalyzer($this->tdbmService->getConnection(), new ArrayCache(), $schemaAnalyzer); $this->tdbmDaoGenerator = new TDBMDaoGenerator($this->getConfiguration(), $tdbmSchemaAnalyzer); - $this->rootPath = __DIR__.'/../'; + $this->rootPath = __DIR__ . '/../'; //$this->tdbmDaoGenerator->setComposerFile($this->rootPath.'composer.json'); } public function testDaoGeneration() { // Remove all previously generated files. - $this->recursiveDelete($this->rootPath.'src/Test/Dao/'); + $this->recursiveDelete($this->rootPath . 'src/Test/Dao/'); $this->tdbmDaoGenerator->generateAllDaosAndBeans(); // Let's require all files to check they are valid PHP! // Test the daoFactory - require_once $this->rootPath.'src/Test/Dao/Generated/DaoFactory.php'; + require_once $this->rootPath . 'src/Test/Dao/Generated/DaoFactory.php'; // Test the others $beanDescriptors = $this->getDummyGeneratorListener()->getBeanDescriptors(); @@ -95,10 +97,10 @@ public function testDaoGeneration() $daoBaseName = $beanDescriptor->getBaseDaoClassName(); $beanName = $beanDescriptor->getBeanClassName(); $baseBeanName = $beanDescriptor->getBaseBeanClassName(); - require_once $this->rootPath.'src/Test/Dao/Bean/Generated/'.$baseBeanName.'.php'; - require_once $this->rootPath.'src/Test/Dao/Bean/'.$beanName.'.php'; - require_once $this->rootPath.'src/Test/Dao/Generated/'.$daoBaseName.'.php'; - require_once $this->rootPath.'src/Test/Dao/'.$daoName.'.php'; + require_once $this->rootPath . 'src/Test/Dao/Bean/Generated/' . $baseBeanName . '.php'; + require_once $this->rootPath . 'src/Test/Dao/Bean/' . $beanName . '.php'; + require_once $this->rootPath . 'src/Test/Dao/Generated/' . $daoBaseName . '.php'; + require_once $this->rootPath . 'src/Test/Dao/' . $daoName . '.php'; } // Check that pivot tables do not generate DAOs or beans. @@ -113,7 +115,7 @@ public function testGenerationException() $schemaAnalyzer = new SchemaAnalyzer($schemaManager); $tdbmSchemaAnalyzer = new TDBMSchemaAnalyzer($this->tdbmService->getConnection(), new ArrayCache(), $schemaAnalyzer); $tdbmDaoGenerator = new TDBMDaoGenerator($configuration, $tdbmSchemaAnalyzer); - $this->rootPath = __DIR__.'/../../../../'; + $this->rootPath = __DIR__ . '/../../../../'; //$tdbmDaoGenerator->setComposerFile($this->rootPath.'composer.json'); $this->expectException(NoPathFoundException::class); @@ -126,7 +128,7 @@ public function testGenerationException() * @param string $str Path to file or directory * @return bool */ - private function recursiveDelete(string $str) : bool + private function recursiveDelete(string $str): bool { if (is_file($str)) { return @unlink($str); @@ -1468,7 +1470,7 @@ public function testPSR2Compliance() // executes after the command finishes if (!$process->isSuccessful()) { echo $process->getOutput(); - $this->fail('Generated code is not PRS2 compliant'); + $this->fail('Generated code is not PSR-2 compliant'); } } @@ -1517,7 +1519,7 @@ public function testTypeHintedConstructors() $userBaseBeanReflectionConstructor = new \ReflectionMethod(UserBaseBean::class, '__construct'); $nameParam = $userBaseBeanReflectionConstructor->getParameters()[0]; - $this->assertSame('string', (string) $nameParam->getType()); + $this->assertSame('string', (string)$nameParam->getType()); } /** @@ -1607,4 +1609,50 @@ public function testRecursiveSave() $leaf->setParent($intermediate); $categoryDao->save($leaf); } + + /** + * @depends testDaoGeneration + */ + public function testBlob() + { + $fp = fopen(__FILE__, 'r'); + $file = new FileBean($fp); + + $fileDao = new FileDao($this->tdbmService); + + $fileDao->save($file); + + $loadedFile = $fileDao->getById($file->getId()); + + $resource = $loadedFile->getFile(); + $result = fseek($resource, 0); + $this->assertSame(0, $result); + $this->assertInternalType('resource', $resource); + $firstLine = fgets($resource); + $this->assertSame("tdbmService); + $loadedFile = $fileDao->getById(1); + + $resource = $loadedFile->getFile(); + $this->assertInternalType('resource', $resource); + $firstLine = fgets($resource); + $this->assertSame("expectException(TDBMInvalidArgumentException::class); + $this->expectExceptionMessage('Invalid argument passed to \'TheCodingMachine\\TDBM\\Test\\Dao\\Bean\\Generated\\FileBaseBean::setFile\'. Expecting a resource. Got a string.'); + new FileBean('foobar'); + } }