From 44d6a7c801b3c783e7eca276bc4a792c5bc3654f Mon Sep 17 00:00:00 2001 From: Joel Alphonso Date: Wed, 11 Aug 2021 16:05:37 -0400 Subject: [PATCH 1/5] :sparkles: feat(property): add support for DatabaseSource new binding parsing and select expressions methods parseBinding() and fieldExpression() were added to StorableProperty to allow for extra mysql query customization --- src/Charcoal/Property/PropertyField.php | 54 +++++++++++++++++++ .../Property/StorablePropertyTrait.php | 53 +++++++++++++++--- 2 files changed, 99 insertions(+), 8 deletions(-) diff --git a/src/Charcoal/Property/PropertyField.php b/src/Charcoal/Property/PropertyField.php index dc1c04ff..35575e6a 100644 --- a/src/Charcoal/Property/PropertyField.php +++ b/src/Charcoal/Property/PropertyField.php @@ -40,11 +40,21 @@ class PropertyField */ private $extra; + /** + * @var mixed + */ + private $selectExpressions; + /** * @var mixed */ private $val; + /** + * @var \Closure + */ + private $parseBinding; + /** * @var mixed */ @@ -86,9 +96,15 @@ public function setData(array $data) if (isset($data['extra'])) { $this->setExtra($data['extra']); } + if (isset($data['selectExpressions'])) { + $this->setSelectExpressions($data['selectExpressions']); + } if (isset($data['val'])) { $this->setVal($data['val']); } + if (isset($data['parseBinding'])) { + $this->setParseBinding($data['parseBinding']); + } if (isset($data['defaultVal'])) { $this->setDefaultVal($data['defaultVal']); } @@ -247,6 +263,25 @@ public function sqlEncoding() return $this->sqlEncoding; } + /** + * @return array + */ + public function selectExpressions() + { + return $this->selectExpressions; + } + + /** + * @param mixed $selectExpressions SqlSelectExpressions for PropertyField. + * @return self + */ + public function setSelectExpressions($selectExpressions) + { + $this->selectExpressions = $selectExpressions; + + return $this; + } + /** * @param mixed $val The field value. * @return PropertyField Chainable @@ -265,6 +300,25 @@ public function val() return $this->val; } + /** + * @return \Closure + */ + public function parseBinding() + { + return $this->parseBinding; + } + + /** + * @param \Closure $parseBinding ParseBinding for PropertyField. + * @return self + */ + public function setParseBinding($parseBinding) + { + $this->parseBinding = $parseBinding; + + return $this; + } + /** * @param mixed $defaultVal The default field value. * @return PropertyField Chainable diff --git a/src/Charcoal/Property/StorablePropertyTrait.php b/src/Charcoal/Property/StorablePropertyTrait.php index 63aa764d..d89d4ef1 100644 --- a/src/Charcoal/Property/StorablePropertyTrait.php +++ b/src/Charcoal/Property/StorablePropertyTrait.php @@ -121,6 +121,23 @@ public function fieldNames() return $this->fieldNames; } + /** + * Overridable to allow for custom Field Name parsing. + * + * This allows to add additional functions to a select statement that looks like this : + * 'MySQL_FUNCTION(select)' + * + * @param string $key The property field key. + * @param mixed $fieldName The raw filed name. + * @return mixed + */ + protected function fieldExpression($key, $fieldName) + { + unset($key); + + return $fieldName; + } + /** * Retrieve the property's value in a format suitable for the given field key. * @@ -190,6 +207,24 @@ public function storageVal($val) return $val; } + /** + * Overridable by property controller to add a custom parsing for the binding identifier. + * + * This allows to add additional functions to an expression to generate a binding statement that looks like this : + * 'MySQL_FUNCTION(:bind)' + * + * @return \Closure + */ + protected function parseBinding() + { + /** + * @param string $bind The PDO bind string. + */ + return function ($bind) { + return $bind; + }; + } + /** * Parse the property's value (from a flattened structure) * in a format suitable for models. @@ -254,14 +289,16 @@ protected function generateFields($val = null) $fieldNames = $this->fieldNames(); foreach ($fieldNames as $fieldKey => $fieldName) { $field = $this->createPropertyField([ - 'ident' => $fieldName, - 'sqlType' => $this->sqlType(), - 'sqlPdoType' => $this->sqlPdoType(), - 'sqlEncoding' => $this->sqlEncoding(), - 'extra' => $this->sqlExtra(), - 'val' => $this->fieldValue($fieldKey, $val), - 'defaultVal' => $this->sqlDefaultVal(), - 'allowNull' => $this['allowNull'], + 'ident' => $fieldName, + 'sqlType' => $this->sqlType(), + 'sqlPdoType' => $this->sqlPdoType(), + 'sqlEncoding' => $this->sqlEncoding(), + 'extra' => $this->sqlExtra(), + 'selectExpressions' => $this->fieldExpression($fieldKey, $fieldName), + 'val' => $this->fieldValue($fieldKey, $val), + 'parseBinding' => $this->parseBinding(), + 'defaultVal' => $this->sqlDefaultVal(), + 'allowNull' => $this['allowNull'], ]); $fields[$fieldKey] = $field; From 79d9eae750eedc78c6268c8527070e81ec77815e Mon Sep 17 00:00:00 2001 From: Joel Alphonso Date: Thu, 12 Aug 2021 11:25:52 -0400 Subject: [PATCH 2/5] :memo: chore(doc): doc comments improvements in StorablePropertyTrait.php --- .../Property/StorablePropertyTrait.php | 50 ++++++++++++++++--- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/src/Charcoal/Property/StorablePropertyTrait.php b/src/Charcoal/Property/StorablePropertyTrait.php index d89d4ef1..c0c15b0d 100644 --- a/src/Charcoal/Property/StorablePropertyTrait.php +++ b/src/Charcoal/Property/StorablePropertyTrait.php @@ -94,13 +94,46 @@ public function fieldIdent($key = null) /** * Retrieve the property's namespaced storage field names. * - * Examples: - * 1. `name`: `name_en`, `name_fr`, `name_de` - * 2. `obj`: `obj_id`, `obj_type` - * 3. `file`: `file`, `file_type` - * 4. `opt`: `opt_0`, `opt_1`, `opt_2` + * This method can be overridden for custom field names. * - * @return string[] + * Example #1 property `name`: + * + * ``` + * { + * "": "name_en" + * } + * ``` + * + * Example #2 multilingual property `title`: + * + * ``` + * { + * "en": "title_en", + * "fr": "title_fr", + * "de": "title_de" + * } + * ``` + * + * Example #3 custom property `obj`: + * + * ``` + * { + * "id": "obj_id", + * "type": "obj_type" + * } + * ``` + * + * Example #4 custom property `tuple`: + * + * ``` + * { + * "1": "tuple_1", + * "2": "tuple_2", + * "3": "tuple_3" + * } + * ``` + * + * @return array<(string|int), string> */ public function fieldNames() { @@ -139,7 +172,10 @@ protected function fieldExpression($key, $fieldName) } /** - * Retrieve the property's value in a format suitable for the given field key. + * Format the property's value for the given field key suitable for storage. + * + * This method can be overridden for parsing custom field values. + * It is recommended to override {@see self::storageVal()} instead. * * @param string $key The property field key. * @param mixed $val The value to set as field value. From f5c22f9f34bf65e5c365d023019328e48a1d6f97 Mon Sep 17 00:00:00 2001 From: Joel Alphonso Date: Thu, 12 Aug 2021 11:33:55 -0400 Subject: [PATCH 3/5] Apply suggestions from code review from @mcaskill refactor: add some clarifications to methods documentation and naming Co-authored-by: Chauncey McAskill --- src/Charcoal/Property/PropertyField.php | 20 +++++--- .../Property/StorablePropertyTrait.php | 49 ++++++++++++------- 2 files changed, 44 insertions(+), 25 deletions(-) diff --git a/src/Charcoal/Property/PropertyField.php b/src/Charcoal/Property/PropertyField.php index 35575e6a..9583cfbe 100644 --- a/src/Charcoal/Property/PropertyField.php +++ b/src/Charcoal/Property/PropertyField.php @@ -96,8 +96,8 @@ public function setData(array $data) if (isset($data['extra'])) { $this->setExtra($data['extra']); } - if (isset($data['selectExpressions'])) { - $this->setSelectExpressions($data['selectExpressions']); + if (isset($data['sqlSelectExpression'])) { + $this->setSqlSelectExpression($data['sqlSelectExpression']); } if (isset($data['val'])) { $this->setVal($data['val']); @@ -264,20 +264,24 @@ public function sqlEncoding() } /** - * @return array + * Retrieve the SQL SELECT field expression. + * + * @return string */ - public function selectExpressions() + public function sqlSelectExpression() { - return $this->selectExpressions; + return $this->sqlSelectExpression ?? $this->ident(); } /** - * @param mixed $selectExpressions SqlSelectExpressions for PropertyField. + * Set the SQL SELECT field expression. + * + * @param string $expression The field expression. * @return self */ - public function setSelectExpressions($selectExpressions) + public function setSqlSelectExpression($expression) { - $this->selectExpressions = $selectExpressions; + $this->sqlSelectExpression = $expression; return $this; } diff --git a/src/Charcoal/Property/StorablePropertyTrait.php b/src/Charcoal/Property/StorablePropertyTrait.php index c0c15b0d..8504c4a1 100644 --- a/src/Charcoal/Property/StorablePropertyTrait.php +++ b/src/Charcoal/Property/StorablePropertyTrait.php @@ -244,20 +244,35 @@ public function storageVal($val) } /** - * Overridable by property controller to add a custom parsing for the binding identifier. + * Format the property's PDO binding parameter identifier. * - * This allows to add additional functions to an expression to generate a binding statement that looks like this : - * 'MySQL_FUNCTION(:bind)' + * This method can be overridden for custom named placeholder parsing. + * + * This method allows a property to apply an SQL function to a named placeholder: + * + * ```sql + * function ($param) { + * return 'ST_GeomFromGeoJSON('.$param.')'; + * } + * ``` + * + * This method returns a closure to be called during the processing of object + * inserts and updates in {@see \Charcoal\Source\DatabaseSource}. + * + * @link https://www.php.net/manual/en/pdostatement.bindparam.php * * @return \Closure */ - protected function parseBinding() + protected function getSqlPdoBindParamExpression() { /** - * @param string $bind The PDO bind string. + * Format the PDO parameter name. + * + * @param string $param A PDO parameter name in the form `:name`. + * @return string The formatted parameter name. */ - return function ($bind) { - return $bind; + return function ($param) { + return $param; }; } @@ -325,16 +340,16 @@ protected function generateFields($val = null) $fieldNames = $this->fieldNames(); foreach ($fieldNames as $fieldKey => $fieldName) { $field = $this->createPropertyField([ - 'ident' => $fieldName, - 'sqlType' => $this->sqlType(), - 'sqlPdoType' => $this->sqlPdoType(), - 'sqlEncoding' => $this->sqlEncoding(), - 'extra' => $this->sqlExtra(), - 'selectExpressions' => $this->fieldExpression($fieldKey, $fieldName), - 'val' => $this->fieldValue($fieldKey, $val), - 'parseBinding' => $this->parseBinding(), - 'defaultVal' => $this->sqlDefaultVal(), - 'allowNull' => $this['allowNull'], + 'ident' => $fieldName, + 'sqlType' => $this->sqlType(), + 'sqlPdoType' => $this->sqlPdoType(), + 'sqlEncoding' => $this->sqlEncoding(), + 'extra' => $this->sqlExtra(), + 'val' => $this->fieldValue($fieldKey, $val), + 'sqlSelectExpression' => $this->getSqlSelectExpression($fieldKey, $fieldName), + 'sqlPdoBindParamExpression' => $this->getSqlPdoBindParamExpression(), + 'defaultVal' => $this->sqlDefaultVal(), + 'allowNull' => $this['allowNull'], ]); $fields[$fieldKey] = $field; From 31bcf6af3ef380255b60cc513ded9ef8c914cdfa Mon Sep 17 00:00:00 2001 From: Joel Alphonso Date: Thu, 12 Aug 2021 12:18:10 -0400 Subject: [PATCH 4/5] :bug: fix: fix some remaining issues with renaming some functions and add some missing documentation --- src/Charcoal/Property/PropertyField.php | 35 +++++++++++++------ .../Property/StorablePropertyTrait.php | 10 +++--- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/Charcoal/Property/PropertyField.php b/src/Charcoal/Property/PropertyField.php index 9583cfbe..c13d1e6a 100644 --- a/src/Charcoal/Property/PropertyField.php +++ b/src/Charcoal/Property/PropertyField.php @@ -43,7 +43,7 @@ class PropertyField /** * @var mixed */ - private $selectExpressions; + private $sqlSelectExpression; /** * @var mixed @@ -53,7 +53,7 @@ class PropertyField /** * @var \Closure */ - private $parseBinding; + private $sqlPdoBindParamExpression; /** * @var mixed @@ -102,8 +102,8 @@ public function setData(array $data) if (isset($data['val'])) { $this->setVal($data['val']); } - if (isset($data['parseBinding'])) { - $this->setParseBinding($data['parseBinding']); + if (isset($data['sqlPdoBindParamExpression'])) { + $this->setSqlPdoBindParamExpression($data['sqlPdoBindParamExpression']); } if (isset($data['defaultVal'])) { $this->setDefaultVal($data['defaultVal']); @@ -270,7 +270,7 @@ public function sqlEncoding() */ public function sqlSelectExpression() { - return $this->sqlSelectExpression ?? $this->ident(); + return ($this->sqlSelectExpression ?? $this->ident()); } /** @@ -305,20 +305,35 @@ public function val() } /** + * Format the property's PDO binding parameter identifier. + * + * This method allows a property to apply an SQL function to a named placeholder: + * + * ```sql + * function ($param) { + * return 'ST_GeomFromGeoJSON('.$param.')'; + * } + * ``` + * + * This method returns a closure to be called during the processing of object + * inserts and updates in {@see \Charcoal\Source\DatabaseSource}. + * + * @link https://www.php.net/manual/en/pdostatement.bindparam.php + * * @return \Closure */ - public function parseBinding() + public function sqlPdoBindParamExpression() { - return $this->parseBinding; + return $this->sqlPdoBindParamExpression; } /** - * @param \Closure $parseBinding ParseBinding for PropertyField. + * @param \Closure $sqlPdoBindParamExpression A callback function to apply an SQL function to a named placeholder. * @return self */ - public function setParseBinding($parseBinding) + public function setSqlPdoBindParamExpression($sqlPdoBindParamExpression) { - $this->parseBinding = $parseBinding; + $this->sqlPdoBindParamExpression = $sqlPdoBindParamExpression; return $this; } diff --git a/src/Charcoal/Property/StorablePropertyTrait.php b/src/Charcoal/Property/StorablePropertyTrait.php index 8504c4a1..fc8984e6 100644 --- a/src/Charcoal/Property/StorablePropertyTrait.php +++ b/src/Charcoal/Property/StorablePropertyTrait.php @@ -164,7 +164,7 @@ public function fieldNames() * @param mixed $fieldName The raw filed name. * @return mixed */ - protected function fieldExpression($key, $fieldName) + protected function sqlSelectExpression($key, $fieldName) { unset($key); @@ -258,12 +258,12 @@ public function storageVal($val) * * This method returns a closure to be called during the processing of object * inserts and updates in {@see \Charcoal\Source\DatabaseSource}. - * + * * @link https://www.php.net/manual/en/pdostatement.bindparam.php * * @return \Closure */ - protected function getSqlPdoBindParamExpression() + protected function sqlPdoBindParamExpression() { /** * Format the PDO parameter name. @@ -346,8 +346,8 @@ protected function generateFields($val = null) 'sqlEncoding' => $this->sqlEncoding(), 'extra' => $this->sqlExtra(), 'val' => $this->fieldValue($fieldKey, $val), - 'sqlSelectExpression' => $this->getSqlSelectExpression($fieldKey, $fieldName), - 'sqlPdoBindParamExpression' => $this->getSqlPdoBindParamExpression(), + 'sqlSelectExpression' => $this->sqlSelectExpression($fieldKey, $fieldName), + 'sqlPdoBindParamExpression' => $this->sqlPdoBindParamExpression(), 'defaultVal' => $this->sqlDefaultVal(), 'allowNull' => $this['allowNull'], ]); From 51b48589cc59f6cd31ce9f599d4d9686f2f591fe Mon Sep 17 00:00:00 2001 From: Joel Alphonso Date: Mon, 16 Aug 2021 16:31:29 -0400 Subject: [PATCH 5/5] :art: refactor: change the sqlSelectExpression method to now return a Closure that can be called from DatabaseSource --- src/Charcoal/Property/PropertyField.php | 23 ++++++++++---- .../Property/StorablePropertyTrait.php | 30 ++++++++++++------- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/Charcoal/Property/PropertyField.php b/src/Charcoal/Property/PropertyField.php index c13d1e6a..4480c63d 100644 --- a/src/Charcoal/Property/PropertyField.php +++ b/src/Charcoal/Property/PropertyField.php @@ -41,7 +41,7 @@ class PropertyField private $extra; /** - * @var mixed + * @var \Closure */ private $sqlSelectExpression; @@ -264,19 +264,32 @@ public function sqlEncoding() } /** - * Retrieve the SQL SELECT field expression. + * Format the property's PDO select statement. * - * @return string + * This method can be overridden for custom select function parsing. + * + * This method allows a property to apply an SQL function to a property select statement: + * + * ```sql + * function ($select) { + * return 'ST_AsGeoJSON('.$select.')'; + * } + * ``` + * + * This method returns a closure to be called during the processing of fetching the object + * or collection in {@see \Charcoal\Source\DatabaseSource}. + * + * @return \Closure */ public function sqlSelectExpression() { - return ($this->sqlSelectExpression ?? $this->ident()); + return $this->sqlSelectExpression; } /** * Set the SQL SELECT field expression. * - * @param string $expression The field expression. + * @param \Closure $expression The field expression. * @return self */ public function setSqlSelectExpression($expression) diff --git a/src/Charcoal/Property/StorablePropertyTrait.php b/src/Charcoal/Property/StorablePropertyTrait.php index fc8984e6..7579611b 100644 --- a/src/Charcoal/Property/StorablePropertyTrait.php +++ b/src/Charcoal/Property/StorablePropertyTrait.php @@ -155,20 +155,28 @@ public function fieldNames() } /** - * Overridable to allow for custom Field Name parsing. + * Format the property's PDO select statement. * - * This allows to add additional functions to a select statement that looks like this : - * 'MySQL_FUNCTION(select)' + * This method can be overridden for custom select function parsing. * - * @param string $key The property field key. - * @param mixed $fieldName The raw filed name. - * @return mixed + * This method allows a property to apply an SQL function to a property select statement: + * + * ```sql + * function ($select) { + * return 'ST_AsGeoJSON('.$select.')'; + * } + * ``` + * + * This method returns a closure to be called during the processing of fetching the object + * or collection in {@see \Charcoal\Source\DatabaseSource}. + * + * @return \Closure */ - protected function sqlSelectExpression($key, $fieldName) + protected function sqlSelectExpression() { - unset($key); - - return $fieldName; + return function ($select) { + return $select; + }; } /** @@ -346,7 +354,7 @@ protected function generateFields($val = null) 'sqlEncoding' => $this->sqlEncoding(), 'extra' => $this->sqlExtra(), 'val' => $this->fieldValue($fieldKey, $val), - 'sqlSelectExpression' => $this->sqlSelectExpression($fieldKey, $fieldName), + 'sqlSelectExpression' => $this->sqlSelectExpression(), 'sqlPdoBindParamExpression' => $this->sqlPdoBindParamExpression(), 'defaultVal' => $this->sqlDefaultVal(), 'allowNull' => $this['allowNull'],