From 54cab6b80755112cf0966c6a9483029a8e959ff1 Mon Sep 17 00:00:00 2001 From: Ori Hoch Date: Thu, 13 Jul 2017 14:15:47 +0300 Subject: [PATCH 1/4] ensure all constraints work --- src/Fields/BaseField.php | 25 ++- tests/FieldTypesTest.php | 339 +++++++++++++++++++++++---------------- 2 files changed, 212 insertions(+), 152 deletions(-) diff --git a/src/Fields/BaseField.php b/src/Fields/BaseField.php index 3be621e..d306033 100644 --- a/src/Fields/BaseField.php +++ b/src/Fields/BaseField.php @@ -292,30 +292,27 @@ protected function checkMaximumConstraint($val, $maxConstraint) return $val <= $maxConstraint; } - protected function checkMinLengthConstraint($val, $minLength) + protected function getLengthForConstraint($val) { if (is_string($val)) { - return strlen($val) >= $minLength; + return strlen($val); } elseif (is_array($val)) { - return count($val) >= $minLength; + return count($val); } elseif (is_object($val)) { - return count($val) >= $minLength; + return count((array)$val); } else { - throw $this->getValidationException('invalid value for minLength constraint', $val); + throw $this->getValidationException('invalid value for length constraint', $val); } } + protected function checkMinLengthConstraint($val, $minLength) + { + return $this->getLengthForConstraint($val) >= $minLength; + } + protected function checkMaxLengthConstraint($val, $maxLength) { - if (is_string($val)) { - return strlen($val) <= $maxLength; - } elseif (is_array($val)) { - return count($val) <= $maxLength; - } elseif (is_object($val)) { - return count($val) <= $maxLength; - } else { - throw $this->getValidationException('invalid value for maxLength constraint', $val); - } + return $this->getLengthForConstraint($val) <= $maxLength; } protected function getAllowedValues() diff --git a/tests/FieldTypesTest.php b/tests/FieldTypesTest.php index c163c70..fe342c4 100644 --- a/tests/FieldTypesTest.php +++ b/tests/FieldTypesTest.php @@ -19,23 +19,23 @@ public function testAny() ['default', '3.14', '3.14'], ['default', true, true], ['default', '', ''], - [(object) ['format' => 'default', 'constraints' => (object) ['required' => true]], + [['format' => 'default', 'constraints' => ['required' => true]], null, null, ], // any field has no empty value, so required without enum is meaningless - [(object) [ + [[ 'format' => 'default', - 'constraints' => (object) ['required' => true, 'enum' => ['test', 1, false]], + 'constraints' => ['required' => true, 'enum' => ['test', 1, false]], ], null, self::ERROR], - [(object) [ + [[ 'format' => 'default', - 'constraints' => (object) ['required' => true, 'enum' => ['test', 1, false]], + 'constraints' => ['required' => true, 'enum' => ['test', 1, false]], ], 'FOO', self::ERROR], - [(object) [ + [[ 'format' => 'default', - 'constraints' => (object) ['required' => true, 'enum' => ['test', 1, false]], + 'constraints' => ['required' => true, 'enum' => ['test', 1, false]], ], false, false], - [(object) [ + [[ 'format' => 'default', - 'constraints' => (object) ['required' => true, 'enum' => ['test', 1, null, false]], + 'constraints' => ['required' => true, 'enum' => ['test', 1, null, false]], ], null, null], ]); } @@ -47,42 +47,42 @@ public function testArray() ['default', '[]', []], ['default', ['key', 'value'], ['key', 'value']], ['default', '["key", "value"]', ['key', 'value']], - ['default', (object) ['key' => 'value'], self::ERROR], + ['default', (object)['key' => 'value'], self::ERROR], ['default', '{"key": "value"}', self::ERROR], ['default', 'string', self::ERROR], ['default', 1, self::ERROR], ['default', '3.14', self::ERROR], ['default', '', self::ERROR], // missingValues is handled at the schema level // required - [(object) ['format' => 'default', 'constraints' => (object) ['required' => true]], + [['format' => 'default', 'constraints' => ['required' => true]], null, self::ERROR, ], - [(object) ['format' => 'default', 'constraints' => (object) ['required' => true]], + [['format' => 'default', 'constraints' => ['required' => true]], [], [], ], // enum - [(object) [ - 'format' => 'default', 'constraints' => (object) ['enum' => [[1, 2], ['foo', 'bar']]], + [[ + 'format' => 'default', 'constraints' => ['enum' => [[1, 2], ['foo', 'bar']]], ], [], self::ERROR], - [(object) [ - 'format' => 'default', 'constraints' => (object) [ + [[ + 'format' => 'default', 'constraints' => [ 'required' => true, 'enum' => [[1, 2], ['foo', 'bar']], ], ], '[1,2]', [1, 2]], - [(object) [ - 'format' => 'default', 'constraints' => (object) ['enum' => [[1, 2], ['foo', 'bar']]], + [[ + 'format' => 'default', 'constraints' => ['enum' => [[1, 2], ['foo', 'bar']]], ], [1, 2], [1, 2]], // minLength / maxLength - [(object) ['format' => 'default', 'constraints' => (object) ['minLength' => 1]], [], self::ERROR], - [(object) ['format' => 'default', 'constraints' => (object) ['minLength' => 1]], [1], [1]], - [(object) ['format' => 'default', 'constraints' => (object) ['minLength' => 1]], [1, 2], [1, 2]], - [(object) ['format' => 'default', 'constraints' => (object) ['minLength' => 1]], 'invalid', self::ERROR], - [(object) ['format' => 'default', 'constraints' => (object) ['maxLength' => 1]], [], []], - [(object) ['format' => 'default', 'constraints' => (object) ['maxLength' => 1]], [1], [1]], - [(object) ['format' => 'default', 'constraints' => (object) ['maxLength' => 1]], [1, 2], self::ERROR], - [(object) ['format' => 'default', 'constraints' => (object) ['minLength' => 1, 'maxLength' => 1]], + [['format' => 'default', 'constraints' => ['minLength' => 1]], [], self::ERROR], + [['format' => 'default', 'constraints' => ['minLength' => 1]], [1], [1]], + [['format' => 'default', 'constraints' => ['minLength' => 1]], [1, 2], [1, 2]], + [['format' => 'default', 'constraints' => ['minLength' => 1]], 'invalid', self::ERROR], + [['format' => 'default', 'constraints' => ['maxLength' => 1]], [], []], + [['format' => 'default', 'constraints' => ['maxLength' => 1]], [1], [1]], + [['format' => 'default', 'constraints' => ['maxLength' => 1]], [1, 2], self::ERROR], + [['format' => 'default', 'constraints' => ['minLength' => 1, 'maxLength' => 1]], [1, 2], self::ERROR, ], - [(object) ['format' => 'default', 'constraints' => (object) ['minLength' => 1, 'maxLength' => 1]], + [['format' => 'default', 'constraints' => ['minLength' => 1, 'maxLength' => 1]], [1], [1], ], - [(object) ['format' => 'default', 'constraints' => (object) ['minLength' => 1, 'maxLength' => 1]], + [['format' => 'default', 'constraints' => ['minLength' => 1, 'maxLength' => 1]], [], self::ERROR, ], ]); } @@ -91,7 +91,7 @@ public function testBoolean() { $this->assertFieldTestData('boolean', [ ['default', true, true], - [(object) ['format' => 'default', 'trueValues' => ['yes']], 'yes', true], + [['format' => 'default', 'trueValues' => ['yes']], 'yes', true], ['default', 'y', self::ERROR], ['default', 'true', true], ['default', 't', self::ERROR], @@ -102,7 +102,7 @@ public function testBoolean() ['default', 'no', self::ERROR], ['default', 'n', self::ERROR], ['default', 'false', false], - [(object) ['format' => 'default', 'falseValues' => ['f']], 'f', false], + [['format' => 'default', 'falseValues' => ['f']], 'f', false], ['default', '0', false], ['default', 'NO', self::ERROR], ['default', 'No', self::ERROR], @@ -111,13 +111,13 @@ public function testBoolean() ['default', '3.14', self::ERROR], ['default', '', self::ERROR], // missingValues is handled at the schema level, // required - [(object) ['format' => 'default', 'constraints' => (object) ['required' => true]], null, self::ERROR], + [['format' => 'default', 'constraints' => ['required' => true]], null, self::ERROR], // enum - [(object) [ - 'format' => 'default', 'constraints' => (object) ['enum' => [false]], + [[ + 'format' => 'default', 'constraints' => ['enum' => [false]], ], true, self::ERROR], - [(object) [ - 'format' => 'default', 'constraints' => (object) ['enum' => [false]], + [[ + 'format' => 'default', 'constraints' => ['enum' => [false]], ], false, false], ]); } @@ -142,14 +142,19 @@ public function testDate() ['%d/%m/%y', '', self::ERROR], // missingValues is handled at the schema level, ['invalid', '21/11/06 16:30', self::ERROR], // required - [(object) ['format' => 'default', 'constraints' => (object) ['required' => true]], null, self::ERROR], + [['format' => 'default', 'constraints' => ['required' => true]], null, self::ERROR], // enum - [(object) [ - 'format' => 'default', 'constraints' => (object) ['enum' => ['2019-01-01']], + [[ + 'format' => 'default', 'constraints' => ['enum' => ['2019-01-01']], ], '2019-01-01', Carbon::create(2019, 1, 1, 0, 0, 0)], - [(object) [ - 'format' => 'default', 'constraints' => (object) ['enum' => ['2019-01-01']], + [[ + 'format' => 'default', 'constraints' => ['enum' => ['2019-01-01']], ], '2019-01-02', self::ERROR], + // minimum / maximum + [['format' => 'default', 'constraints' => ['minimum' => '2019-01-01']], '2018-12-28', self::ERROR], + [['format' => 'default', 'constraints' => ['maximum' => '2019-01-01']], '2019-01-02', self::ERROR], + [['format' => 'default', 'constraints' => ['minimum' => '2019-01-01', 'maximum' => '2019-01-01']], '2019-01-01', Carbon::create(2019, 1, 1, 0, 0, 0)], + [['format' => 'default', 'constraints' => ['minimum' => '2019-01-01', 'maximum' => '2019-01-04']], '2019-01-01', Carbon::create(2019, 1, 1, 0, 0, 0)], ]); } @@ -173,14 +178,19 @@ public function testDatetime() ['%d/%m/%y %H:%M', '', self::ERROR], // missingValues is handled at the schema level, ['invalid', '21/11/06 16:30', self::ERROR], // required - [(object) ['format' => 'default', 'constraints' => (object) ['required' => true]], null, self::ERROR], + [['format' => 'default', 'constraints' => ['required' => true]], null, self::ERROR], // enum - [(object) [ - 'format' => 'default', 'constraints' => (object) ['enum' => ['2014-01-01T06:00:00Z']], + [[ + 'format' => 'default', 'constraints' => ['enum' => ['2014-01-01T06:00:00Z']], ], '2014-01-01T06:00:00Z', Carbon::create(2014, 1, 1, 6, 0, 0, 'UTC')], - [(object) [ - 'format' => 'default', 'constraints' => (object) ['enum' => ['2014-01-01T06:00:00Z']], + [[ + 'format' => 'default', 'constraints' => ['enum' => ['2014-01-01T06:00:00Z']], ], '2014-01-01T06:01:00Z', self::ERROR], + // minimum / maximum + [['format' => 'default', 'constraints' => ['minimum' => '2014-01-01T06:35:21Z']], '2014-01-01T06:35:20Z', self::ERROR], + [['format' => 'default', 'constraints' => ['maximum' => '2014-01-01T06:35:21Z']], '2014-01-01T06:35:22Z', self::ERROR], + [['format' => 'default', 'constraints' => ['minimum' => '2014-01-01T06:35:21Z', 'maximum' => '2014-01-01T06:35:21Z']], '2014-01-01T06:35:21Z', Carbon::create(2014, 1, 1, 6, 35, 21, 'UTC')], + [['format' => 'default', 'constraints' => ['minimum' => '2014-01-01T06:35:21Z', 'maximum' => '2014-01-01T06:35:22Z']], '2014-01-01T06:35:21Z', Carbon::create(2014, 1, 1, 6, 35, 21, 'UTC')], ]); } @@ -198,15 +208,15 @@ public function testDuration() ['default', 1, self::ERROR], ['default', '', self::ERROR], // missingValues is handled at the schema level, ['default', [], self::ERROR], - ['default', (object) [], self::ERROR], + ['default', [], self::ERROR], // required - [(object) ['format' => 'default', 'constraints' => (object) ['required' => true]], null, self::ERROR], + [['format' => 'default', 'constraints' => ['required' => true]], null, self::ERROR], // enum - [(object) [ - 'format' => 'default', 'constraints' => (object) ['enum' => ['P1Y10M3DT5H11M7S']], + [[ + 'format' => 'default', 'constraints' => ['enum' => ['P1Y10M3DT5H11M7S']], ], 'P1Y10M3DT5H11M7S', new CarbonInterval(1, 10, 0, 3, 5, 11, 7)], - [(object) [ - 'format' => 'default', 'constraints' => (object) ['enum' => ['P1Y10M3DT5H11M7S']], + [[ + 'format' => 'default', 'constraints' => ['enum' => ['P1Y10M3DT5H11M7S']], ], 'P1Y10M3DT5H11M8S', self::ERROR], ]); } @@ -216,47 +226,47 @@ public function testGeojson() $this->assertFieldTestData('geojson', [ [ 'default', - (object) ['properties' => (object) ['Ã' => 'Ã'], 'type' => 'Feature', 'geometry' => null], - (object) ['properties' => (object) ['Ã' => 'Ã'], 'type' => 'Feature', 'geometry' => null], + ['properties' => ['Ã' => 'Ã'], 'type' => 'Feature', 'geometry' => null], + ['properties' => ['Ã' => 'Ã'], 'type' => 'Feature', 'geometry' => null], ], [ 'default', '{"geometry": null, "type": "Feature", "properties": {"\\u00c3": "\\u00c3"}}', - (object) ['properties' => (object) ['Ã' => 'Ã'], 'type' => 'Feature', 'geometry' => null], + ['properties' => ['Ã' => 'Ã'], 'type' => 'Feature', 'geometry' => null], ], [ 'default', - (object) ['coordinates' => [0, 0, 0], 'type' => 'Point'], - (object) ['coordinates' => [0, 0, 0], 'type' => 'Point'], + ['coordinates' => [0, 0, 0], 'type' => 'Point'], + ['coordinates' => [0, 0, 0], 'type' => 'Point'], ], ['default', 'string', self::ERROR], ['default', 1, self::ERROR], ['default', '3.14', self::ERROR], ['default', '', self::ERROR], // missingValues is handled at the schema level, - ['default', (object) [], self::ERROR], + ['default', [], self::ERROR], ['default', '{}', self::ERROR], [ 'topojson', - (object) ['type' => 'LineString', 'arcs' => [42]], - (object) ['type' => 'LineString', 'arcs' => [42]], + ['type' => 'LineString', 'arcs' => [42]], + ['type' => 'LineString', 'arcs' => [42]], ], [ 'topojson', '{"type": "LineString", "arcs": [42]}', - (object) ['type' => 'LineString', 'arcs' => [42]], + ['type' => 'LineString', 'arcs' => [42]], ], ['topojson', 'string', self::ERROR], ['topojson', 1, self::ERROR], ['topojson', '3.14', self::ERROR], ['topojson', '', self::ERROR], // required - [(object) ['format' => 'default', 'constraints' => (object) ['required' => true]], null, self::ERROR], + [['format' => 'default', 'constraints' => ['required' => true]], null, self::ERROR], // enum - [(object) [ - 'format' => 'default', 'constraints' => (object) ['enum' => ['{"geometry": null, "type": "Feature", "properties": {"\\u00c3": "\\u00c3"}}']], - ], '{"geometry": null, "type": "Feature", "properties": {"\\u00c3": "\\u00c3"}}', (object) ['properties' => (object) ['Ã' => 'Ã'], 'type' => 'Feature', 'geometry' => null]], - [(object) [ - 'format' => 'default', 'constraints' => (object) ['enum' => ['{"geometry": null, "type": "Feature", "properties": {"\\u00c3": "\\u00c3"}}']], + [[ + 'format' => 'default', 'constraints' => ['enum' => ['{"geometry": null, "type": "Feature", "properties": {"\\u00c3": "\\u00c3"}}']], + ], '{"geometry": null, "type": "Feature", "properties": {"\\u00c3": "\\u00c3"}}', ['properties' => ['Ã' => 'Ã'], 'type' => 'Feature', 'geometry' => null]], + [[ + 'format' => 'default', 'constraints' => ['enum' => ['{"geometry": null, "type": "Feature", "properties": {"\\u00c3": "\\u00c3"}}']], ], '{"geometry": null, "type": "Feature", "properties": {"\\u00c3": "\\u00c4"}}', self::ERROR], ]); } @@ -268,7 +278,7 @@ public function testGeopoint() ['default', [180, 90], self::ERROR], ['default', '180,90', [180, 90]], ['default', '180, -90', [180, -90]], - ['default', (object) ['lon' => 180, 'lat' => 90], self::ERROR], + ['default', ['lon' => 180, 'lat' => 90], self::ERROR], ['default', '181,90', self::ERROR], ['default', '0,91', self::ERROR], ['default', 'string', self::ERROR], @@ -278,7 +288,7 @@ public function testGeopoint() ['array', [180, 90], [180, 90]], ['array', [180, 90], [180, 90]], ['array', '[180, -90]', [180, -90]], - ['array', (object) ['lon' => 180, 'lat' => 90], self::ERROR], + ['array', ['lon' => 180, 'lat' => 90], self::ERROR], ['array', [181, 90], self::ERROR], ['array', [0, 91], self::ERROR], ['array', '180,90', self::ERROR], @@ -286,12 +296,12 @@ public function testGeopoint() ['array', 1, self::ERROR], ['array', '3.14', self::ERROR], ['array', '', self::ERROR], // missingValues is handled at the schema level, - ['object', (object) ['lon' => 180, 'lat' => 90], [180, 90]], + ['object', ['lon' => 180, 'lat' => 90], [180, 90]], ['object', '{"lon":180, "lat":90}', [180, 90]], ['object', '{"lat":90, "lon":180, "foo": "bar"}', [180, 90]], ['object', '[180, -90]', self::ERROR], - ['object', (object) ['lon' => 181, 'lat' => 90], self::ERROR], - ['object', (object) ['lon' => 180, 'lat' => -91], self::ERROR], + ['object', ['lon' => 181, 'lat' => 90], self::ERROR], + ['object', ['lon' => 180, 'lat' => -91], self::ERROR], ['object', [180, -90], self::ERROR], ['object', '180,90', self::ERROR], ['object', 'string', self::ERROR], @@ -299,10 +309,10 @@ public function testGeopoint() ['object', '3.14', self::ERROR], ['object', '', self::ERROR], // missingValues is handled at the schema level, // required - [(object) ['format' => 'default', 'constraints' => (object) ['required' => true]], null, self::ERROR], + [['format' => 'default', 'constraints' => ['required' => true]], null, self::ERROR], // enum - [(object) ['format' => 'array', 'constraints' => (object) ['enum' => ['[180, -90]']]], '[180, -90]', [180, -90]], - [(object) ['format' => 'array', 'constraints' => (object) ['enum' => ['[180, -90]']]], '[171, -90]', self::ERROR], + [['format' => 'array', 'constraints' => ['enum' => ['[180, -90]']]], '[180, -90]', [180, -90]], + [['format' => 'array', 'constraints' => ['enum' => ['[180, -90]']]], '[171, -90]', self::ERROR], ]); } @@ -314,74 +324,97 @@ public function testInteger() ['default', '3.14', self::ERROR], ['default', '', self::ERROR], // missingValues is handled at the schema level, // required - [(object) ['format' => 'default', 'constraints' => (object) ['required' => true]], null, self::ERROR], + [['format' => 'default', 'constraints' => ['required' => true]], null, self::ERROR], // enum - [(object) ['format' => 'default', 'constraints' => (object) ['enum' => [0, '1']]], '0', 0], - [(object) ['format' => 'default', 'constraints' => (object) ['enum' => [0, '1']]], '2', self::ERROR], + [['format' => 'default', 'constraints' => ['enum' => [0, '1']]], '0', 0], + [['format' => 'default', 'constraints' => ['enum' => [0, '1']]], '2', self::ERROR], + // minimum / maximum + [['format' => 'default', 'constraints' => ['minimum' => 2]], 1, self::ERROR], + [['format' => 'default', 'constraints' => ['maximum' => '0']], 1, self::ERROR], + [['format' => 'default', 'constraints' => ['maximum' => "1", 'minimum' => 1]], "1", 1], ]); } public function testNumber() { $this->assertFieldTestData('number', [ - [(object) ['format' => 'default'], 1, 1.0], - [(object) ['format' => 'default'], 1, 1.0], - [(object) ['format' => 'default'], 1.0, 1.0], - [(object) ['format' => 'default'], '1', 1.0], - [(object) ['format' => 'default'], '10.00', 10.0], - [(object) ['format' => 'default'], '10.50', 10.5], - [(object) ['format' => 'default'], '100%', 1.0], - [(object) ['format' => 'default'], '1000‰', self::ERROR], // spec only supports percent sign - [(object) ['format' => 'default'], '-1000', -1000.0], - [(object) ['format' => 'default', 'groupChar' => ','], '1,000', 1000.0], - [(object) ['format' => 'default', 'groupChar' => ','], '10,000.00', 10000.0], - [(object) ['format' => 'default', 'groupChar' => ','], '10,000,000.50', 10000000.5], - [(object) ['format' => 'default', 'groupChar' => '#'], '10#000.00', 10000.0], - [(object) ['format' => 'default', 'groupChar' => '#'], '10#000#000.50', 10000000.5], - [(object) ['format' => 'default', 'groupChar' => '#'], '10.50', 10.5], - [(object) ['format' => 'default', 'groupChar' => '#'], '1#000', 1000.0], - [(object) ['format' => 'default', 'groupChar' => '#', 'decimalChar' => '@'], '10#000@00', 10000.0], - [(object) ['format' => 'default', 'groupChar' => '#', 'decimalChar' => '@'], '10#000#000@50', 10000000.5], - [(object) ['format' => 'default', 'groupChar' => '#', 'decimalChar' => '@'], '10@50', 10.5], - [(object) ['format' => 'default', 'groupChar' => '#', 'decimalChar' => '@'], '1#000', 1000.0], - [(object) ['format' => 'default', 'groupChar' => ',', 'currency' => true], '10,000.00', 10000.0], - [(object) ['format' => 'default', 'groupChar' => ',', 'currency' => true], '10,000,000.00', 10000000.0], - [(object) ['format' => 'default', 'currency' => true], '$10000.00', 10000.0], - [(object) ['format' => 'default', 'groupChar' => ',', 'currency' => true], ' 10,000.00 €', 10000.0], - [(object) ['format' => 'default', 'groupChar' => ' ', 'decimalChar' => ','], '10 000,00', 10000.0], - [(object) ['format' => 'default', 'groupChar' => ' ', 'decimalChar' => ','], '10 000 000,00', 10000000.0], - [(object) ['format' => 'default', 'groupChar' => ' ', 'decimalChar' => ',', 'currency' => true], '10000,00 ₪', 10000.0], - [(object) ['format' => 'default', 'groupChar' => ' ', 'decimalChar' => ',', 'currency' => true], ' 10 000,00 £', 10000.0], - [(object) ['format' => 'default'], '10,000a.00', self::ERROR], - [(object) ['format' => 'default'], '10+000.00', self::ERROR], - [(object) ['format' => 'default'], '$10:000.00', self::ERROR], - [(object) ['format' => 'default'], 'string', self::ERROR], - [(object) ['format' => 'default'], '', self::ERROR], // missingValues is handled at the schema level, + [['format' => 'default'], 1, 1.0], + [['format' => 'default'], 1, 1.0], + [['format' => 'default'], 1.0, 1.0], + [['format' => 'default'], '1', 1.0], + [['format' => 'default'], '10.00', 10.0], + [['format' => 'default'], '10.50', 10.5], + [['format' => 'default'], '100%', 1.0], + [['format' => 'default'], '1000‰', self::ERROR], // spec only supports percent sign + [['format' => 'default'], '-1000', -1000.0], + [['format' => 'default', 'groupChar' => ','], '1,000', 1000.0], + [['format' => 'default', 'groupChar' => ','], '10,000.00', 10000.0], + [['format' => 'default', 'groupChar' => ','], '10,000,000.50', 10000000.5], + [['format' => 'default', 'groupChar' => '#'], '10#000.00', 10000.0], + [['format' => 'default', 'groupChar' => '#'], '10#000#000.50', 10000000.5], + [['format' => 'default', 'groupChar' => '#'], '10.50', 10.5], + [['format' => 'default', 'groupChar' => '#'], '1#000', 1000.0], + [['format' => 'default', 'groupChar' => '#', 'decimalChar' => '@'], '10#000@00', 10000.0], + [['format' => 'default', 'groupChar' => '#', 'decimalChar' => '@'], '10#000#000@50', 10000000.5], + [['format' => 'default', 'groupChar' => '#', 'decimalChar' => '@'], '10@50', 10.5], + [['format' => 'default', 'groupChar' => '#', 'decimalChar' => '@'], '1#000', 1000.0], + [['format' => 'default', 'groupChar' => ',', 'currency' => true], '10,000.00', 10000.0], + [['format' => 'default', 'groupChar' => ',', 'currency' => true], '10,000,000.00', 10000000.0], + [['format' => 'default', 'currency' => true], '$10000.00', 10000.0], + [['format' => 'default', 'groupChar' => ',', 'currency' => true], ' 10,000.00 €', 10000.0], + [['format' => 'default', 'groupChar' => ' ', 'decimalChar' => ','], '10 000,00', 10000.0], + [['format' => 'default', 'groupChar' => ' ', 'decimalChar' => ','], '10 000 000,00', 10000000.0], + [['format' => 'default', 'groupChar' => ' ', 'decimalChar' => ',', 'currency' => true], '10000,00 ₪', 10000.0], + [['format' => 'default', 'groupChar' => ' ', 'decimalChar' => ',', 'currency' => true], ' 10 000,00 £', 10000.0], + [['format' => 'default'], '10,000a.00', self::ERROR], + [['format' => 'default'], '10+000.00', self::ERROR], + [['format' => 'default'], '$10:000.00', self::ERROR], + [['format' => 'default'], 'string', self::ERROR], + [['format' => 'default'], '', self::ERROR], // missingValues is handled at the schema level, // required - [(object) ['format' => 'default', 'constraints' => (object) ['required' => true]], null, self::ERROR], + [['format' => 'default', 'constraints' => ['required' => true]], null, self::ERROR], // enum - [(object) ['format' => 'default', 'constraints' => (object) ['enum' => [0.5, '1.6']]], '1.6', 1.6], - [(object) ['format' => 'default', 'constraints' => (object) ['enum' => [0.5, '1.6']]], '0.55', self::ERROR], + [['format' => 'default', 'constraints' => ['enum' => [0.5, '1.6']]], '1.6', 1.6], + [['format' => 'default', 'constraints' => ['enum' => [0.5, '1.6']]], '0.55', self::ERROR], + // minimum / maximum + [['format' => 'default', 'constraints' => ['minimum' => 2]], 1.4, self::ERROR], + [['format' => 'default', 'constraints' => ['maximum' => '0']], 1.2, self::ERROR], + [['format' => 'default', 'constraints' => ['minimum' => 1.1, 'maximum' => "1.1"]], "1.1", 1.1], + [['format' => 'default', 'constraints' => ['minimum' => 1.2, 'maximum' => "1.4"]], "1.2", 1.2], ]); } public function testObject() { $this->assertFieldTestData('object', [ - ['default', (object) [], (object) []], - ['default', '{}', (object) []], - ['default', (object) ['key' => 'value'], (object) ['key' => 'value']], - ['default', '{"key": "value"}', (object) ['key' => 'value']], + ['default', [], []], + ['default', '{}', []], + ['default', ['key' => 'value'], ['key' => 'value']], + ['default', '{"key": "value"}', ['key' => 'value']], ['default', '["key", "value"]', self::ERROR], ['default', 'string', self::ERROR], ['default', 1, self::ERROR], ['default', '3.14', self::ERROR], ['default', '', self::ERROR], // missingValues is handled at the schema level, // required - [(object) ['format' => 'default', 'constraints' => (object) ['required' => true]], null, self::ERROR], + [['format' => 'default', 'constraints' => ['required' => true]], null, self::ERROR], // enum - [(object) ['format' => 'default', 'constraints' => (object) ['enum' => ['{"foo":"bar"}']]], '{"foo":"bar"}', (object) ['foo' => 'bar']], - [(object) ['format' => 'default', 'constraints' => (object) ['enum' => ['{"foo":"bar"}']]], '{"foox":"bar"}', self::ERROR], + [['format' => 'default', 'constraints' => ['enum' => ['{"foo":"bar"}']]], '{"foo":"bar"}', ['foo' => 'bar']], + [['format' => 'default', 'constraints' => ['enum' => ['{"foo":"bar"}']]], '{"foox":"bar"}', self::ERROR], + // minLength / maxLength + [['format' => 'default', 'constraints' => ['minLength' => 1]], '{}', self::ERROR], + [['format' => 'default', 'constraints' => ['minLength' => 1]], '{"a":1}', (object)["a"=>1]], + [['format' => 'default', 'constraints' => ['minLength' => 1]], '{"a":1, "b":2}', (object)["a"=>1,"b"=>2]], + [['format' => 'default', 'constraints' => ['minLength' => 1]], 'invalid', self::ERROR], + [['format' => 'default', 'constraints' => ['maxLength' => 1]], '{}', (object)[]], + [['format' => 'default', 'constraints' => ['maxLength' => 1]], '{"a":1}', (object)["a"=>1]], + [['format' => 'default', 'constraints' => ['maxLength' => 1]], '{"a":1, "b":2}', self::ERROR], + [['format' => 'default', 'constraints' => ['minLength' => 1, 'maxLength' => 1]], + '{"a":1, "b":2}', self::ERROR, ], + [['format' => 'default', 'constraints' => ['minLength' => 1, 'maxLength' => 1]], + '{"a":1}', (object)["a"=>1], ], + [['format' => 'default', 'constraints' => ['minLength' => 1, 'maxLength' => 1]], + '{}', self::ERROR, ], ]); } @@ -404,11 +437,27 @@ public function testString() ['binary', 'dGVzdA==', 'dGVzdA=='], ['binary', '', ''], // required - [(object) ['format' => 'default', 'constraints' => (object) ['required' => true]], '', ''], - [(object) ['format' => 'default', 'constraints' => (object) ['required' => true]], null, self::ERROR], + [['format' => 'default', 'constraints' => ['required' => true]], '', ''], + [['format' => 'default', 'constraints' => ['required' => true]], null, self::ERROR], // enum - [(object) ['format' => 'default', 'constraints' => (object) ['enum' => ['foobar']]], 'foobar', 'foobar'], - [(object) ['format' => 'default', 'constraints' => (object) ['enum' => ['foobar']]], 'foobarx', self::ERROR], + [['format' => 'default', 'constraints' => ['enum' => ['foobar']]], 'foobar', 'foobar'], + [['format' => 'default', 'constraints' => ['enum' => ['foobar']]], 'foobarx', self::ERROR], + // minLength / maxLength + [['format' => 'default', 'constraints' => ['minLength' => 1]], "", self::ERROR], + [['format' => 'default', 'constraints' => ['minLength' => 1]], "a", "a"], + [['format' => 'default', 'constraints' => ['minLength' => 1]], "ab", "ab"], + [['format' => 'default', 'constraints' => ['maxLength' => 1]], "", ""], + [['format' => 'default', 'constraints' => ['maxLength' => 1]], "a", "a"], + [['format' => 'default', 'constraints' => ['maxLength' => 1]], "ab", self::ERROR], + [['format' => 'default', 'constraints' => ['minLength' => 1, 'maxLength' => 1]], + "ab", self::ERROR, ], + [['format' => 'default', 'constraints' => ['minLength' => 1, 'maxLength' => 1]], + "a", "a", ], + [['format' => 'default', 'constraints' => ['minLength' => 1, 'maxLength' => 1]], + "", self::ERROR, ], + // pattern + [['format' => 'default', 'constraints' => ['pattern' => '^[a-z]*$']], 'AAA', self::ERROR], + [['format' => 'default', 'constraints' => ['pattern' => '^[a-z]*$']], 'aaa', 'aaa'] ]); } @@ -442,10 +491,15 @@ public function testTime() ['%H:%M:%S', '06:35:21', [6, 35, 21]], ['%H:%M', '06:35:21', self::ERROR], // required - [(object) ['format' => 'default', 'constraints' => (object) ['required' => true]], null, self::ERROR], + [['format' => 'default', 'constraints' => ['required' => true]], null, self::ERROR], // enum - [(object) ['format' => 'default', 'constraints' => (object) ['enum' => ['06:00:00']]], '06:00:00', [6, 0, 0]], - [(object) ['format' => 'default', 'constraints' => (object) ['enum' => ['06:00:00']]], '06:01:00', self::ERROR], + [['format' => 'default', 'constraints' => ['enum' => ['06:00:00']]], '06:00:00', [6, 0, 0]], + [['format' => 'default', 'constraints' => ['enum' => ['06:00:00']]], '06:01:00', self::ERROR], + // minimum / maximum + [['format' => 'default', 'constraints' => ['minimum' => '06:35:21']], '06:35:20', self::ERROR], + [['format' => 'default', 'constraints' => ['maximum' => '06:35:21']], '06:35:22', self::ERROR], + [['format' => 'default', 'constraints' => ['minimum' => '06:35:21', 'maximum' => '06:35:21']], '06:35:21', [6, 35, 21]], + [['format' => 'default', 'constraints' => ['minimum' => '06:35:21', 'maximum' => '06:35:22']], '06:35:21', [6, 35, 21]], ]); } @@ -458,10 +512,14 @@ public function testYear() ['default', 20000, 20000], ['default', '3.14', self::ERROR], // required - [(object) ['format' => 'default', 'constraints' => (object) ['required' => true]], null, self::ERROR], + [['format' => 'default', 'constraints' => ['required' => true]], null, self::ERROR], // enum - [(object) ['format' => 'default', 'constraints' => (object) ['enum' => [2000]]], '2000', 2000], - [(object) ['format' => 'default', 'constraints' => (object) ['enum' => [2000]]], '2001', self::ERROR], + [['format' => 'default', 'constraints' => ['enum' => [2000]]], '2000', 2000], + [['format' => 'default', 'constraints' => ['enum' => [2000]]], '2001', self::ERROR], + // minimum / maximum + [['format' => 'default', 'constraints' => ['minimum' => '2000']], '1999', self::ERROR], + [['format' => 'default', 'constraints' => ['maximum' => '2000']], '2001', self::ERROR], + [['format' => 'default', 'constraints' => ['minimum' => '2000', 'maximum' => '2000']], '2000', 2000], ]); } @@ -482,10 +540,15 @@ public function testYearMonth() ['default', '3.14', self::ERROR], ['default', '', self::ERROR], // required - [(object) ['format' => 'default', 'constraints' => (object) ['required' => true]], null, self::ERROR], + [['format' => 'default', 'constraints' => ['required' => true]], null, self::ERROR], // enum - [(object) ['format' => 'default', 'constraints' => (object) ['enum' => [[2000, 10]]]], '2000-10', [2000, 10]], - [(object) ['format' => 'default', 'constraints' => (object) ['enum' => [[2000, 10]]]], '2000-11', self::ERROR], + [['format' => 'default', 'constraints' => ['enum' => [[2000, 10]]]], '2000-10', [2000, 10]], + [['format' => 'default', 'constraints' => ['enum' => [[2000, 10]]]], '2000-10', [2000, 10]], + [['format' => 'default', 'constraints' => ['enum' => [[2000, 10]]]], '2000-11', self::ERROR], + // minimum / maximum + [['format' => 'default', 'constraints' => ['minimum' => '2000-10']], '1999-12', self::ERROR], + [['format' => 'default', 'constraints' => ['maximum' => '2000-10']], '2001-01', self::ERROR], + [['format' => 'default', 'constraints' => ['minimum' => '2000-10', 'maximum' => '2000-10']], '2000-10', [2000, 10]], ]); } @@ -496,15 +559,15 @@ protected function assertFieldTestData($fieldType, $testData) $testLine[3] = null; } list($format, $inputValue, $expectedCastValue, $expectedInferType) = $testLine; - if (is_object($format)) { + if (is_array($format)) { $descriptor = $format; - $descriptor->type = $fieldType; + $descriptor["type"] = $fieldType; } else { - $descriptor = (object) ['type' => $fieldType, 'format' => $format]; + $descriptor = ['type' => $fieldType, 'format' => $format]; } $assertMessage = 'descriptor='.json_encode($descriptor).", input='".json_encode($inputValue)."', expected='".json_encode($expectedCastValue)."'"; - if (!isset($descriptor->name)) { - $descriptor->name = 'unknown'; + if (!isset($descriptor["name"])) { + $descriptor["name"] = 'unknown'; } $field = FieldsFactory::field($descriptor); if ($expectedCastValue === self::ERROR) { From 53f2a7995ff2d1ba38e3b96c25e97e7f8f01ab6d Mon Sep 17 00:00:00 2001 From: Ori Hoch Date: Thu, 13 Jul 2017 14:17:20 +0300 Subject: [PATCH 2/4] style fix --- src/Fields/BaseField.php | 2 +- tests/FieldTypesTest.php | 44 ++++++++++++++++++++-------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/Fields/BaseField.php b/src/Fields/BaseField.php index d306033..286dc87 100644 --- a/src/Fields/BaseField.php +++ b/src/Fields/BaseField.php @@ -299,7 +299,7 @@ protected function getLengthForConstraint($val) } elseif (is_array($val)) { return count($val); } elseif (is_object($val)) { - return count((array)$val); + return count((array) $val); } else { throw $this->getValidationException('invalid value for length constraint', $val); } diff --git a/tests/FieldTypesTest.php b/tests/FieldTypesTest.php index fe342c4..877be04 100644 --- a/tests/FieldTypesTest.php +++ b/tests/FieldTypesTest.php @@ -47,7 +47,7 @@ public function testArray() ['default', '[]', []], ['default', ['key', 'value'], ['key', 'value']], ['default', '["key", "value"]', ['key', 'value']], - ['default', (object)['key' => 'value'], self::ERROR], + ['default', (object) ['key' => 'value'], self::ERROR], ['default', '{"key": "value"}', self::ERROR], ['default', 'string', self::ERROR], ['default', 1, self::ERROR], @@ -331,7 +331,7 @@ public function testInteger() // minimum / maximum [['format' => 'default', 'constraints' => ['minimum' => 2]], 1, self::ERROR], [['format' => 'default', 'constraints' => ['maximum' => '0']], 1, self::ERROR], - [['format' => 'default', 'constraints' => ['maximum' => "1", 'minimum' => 1]], "1", 1], + [['format' => 'default', 'constraints' => ['maximum' => '1', 'minimum' => 1]], '1', 1], ]); } @@ -379,8 +379,8 @@ public function testNumber() // minimum / maximum [['format' => 'default', 'constraints' => ['minimum' => 2]], 1.4, self::ERROR], [['format' => 'default', 'constraints' => ['maximum' => '0']], 1.2, self::ERROR], - [['format' => 'default', 'constraints' => ['minimum' => 1.1, 'maximum' => "1.1"]], "1.1", 1.1], - [['format' => 'default', 'constraints' => ['minimum' => 1.2, 'maximum' => "1.4"]], "1.2", 1.2], + [['format' => 'default', 'constraints' => ['minimum' => 1.1, 'maximum' => '1.1']], '1.1', 1.1], + [['format' => 'default', 'constraints' => ['minimum' => 1.2, 'maximum' => '1.4']], '1.2', 1.2], ]); } @@ -403,16 +403,16 @@ public function testObject() [['format' => 'default', 'constraints' => ['enum' => ['{"foo":"bar"}']]], '{"foox":"bar"}', self::ERROR], // minLength / maxLength [['format' => 'default', 'constraints' => ['minLength' => 1]], '{}', self::ERROR], - [['format' => 'default', 'constraints' => ['minLength' => 1]], '{"a":1}', (object)["a"=>1]], - [['format' => 'default', 'constraints' => ['minLength' => 1]], '{"a":1, "b":2}', (object)["a"=>1,"b"=>2]], + [['format' => 'default', 'constraints' => ['minLength' => 1]], '{"a":1}', (object) ['a' => 1]], + [['format' => 'default', 'constraints' => ['minLength' => 1]], '{"a":1, "b":2}', (object) ['a' => 1, 'b' => 2]], [['format' => 'default', 'constraints' => ['minLength' => 1]], 'invalid', self::ERROR], - [['format' => 'default', 'constraints' => ['maxLength' => 1]], '{}', (object)[]], - [['format' => 'default', 'constraints' => ['maxLength' => 1]], '{"a":1}', (object)["a"=>1]], + [['format' => 'default', 'constraints' => ['maxLength' => 1]], '{}', (object) []], + [['format' => 'default', 'constraints' => ['maxLength' => 1]], '{"a":1}', (object) ['a' => 1]], [['format' => 'default', 'constraints' => ['maxLength' => 1]], '{"a":1, "b":2}', self::ERROR], [['format' => 'default', 'constraints' => ['minLength' => 1, 'maxLength' => 1]], '{"a":1, "b":2}', self::ERROR, ], [['format' => 'default', 'constraints' => ['minLength' => 1, 'maxLength' => 1]], - '{"a":1}', (object)["a"=>1], ], + '{"a":1}', (object) ['a' => 1], ], [['format' => 'default', 'constraints' => ['minLength' => 1, 'maxLength' => 1]], '{}', self::ERROR, ], ]); @@ -443,21 +443,21 @@ public function testString() [['format' => 'default', 'constraints' => ['enum' => ['foobar']]], 'foobar', 'foobar'], [['format' => 'default', 'constraints' => ['enum' => ['foobar']]], 'foobarx', self::ERROR], // minLength / maxLength - [['format' => 'default', 'constraints' => ['minLength' => 1]], "", self::ERROR], - [['format' => 'default', 'constraints' => ['minLength' => 1]], "a", "a"], - [['format' => 'default', 'constraints' => ['minLength' => 1]], "ab", "ab"], - [['format' => 'default', 'constraints' => ['maxLength' => 1]], "", ""], - [['format' => 'default', 'constraints' => ['maxLength' => 1]], "a", "a"], - [['format' => 'default', 'constraints' => ['maxLength' => 1]], "ab", self::ERROR], + [['format' => 'default', 'constraints' => ['minLength' => 1]], '', self::ERROR], + [['format' => 'default', 'constraints' => ['minLength' => 1]], 'a', 'a'], + [['format' => 'default', 'constraints' => ['minLength' => 1]], 'ab', 'ab'], + [['format' => 'default', 'constraints' => ['maxLength' => 1]], '', ''], + [['format' => 'default', 'constraints' => ['maxLength' => 1]], 'a', 'a'], + [['format' => 'default', 'constraints' => ['maxLength' => 1]], 'ab', self::ERROR], [['format' => 'default', 'constraints' => ['minLength' => 1, 'maxLength' => 1]], - "ab", self::ERROR, ], + 'ab', self::ERROR, ], [['format' => 'default', 'constraints' => ['minLength' => 1, 'maxLength' => 1]], - "a", "a", ], + 'a', 'a', ], [['format' => 'default', 'constraints' => ['minLength' => 1, 'maxLength' => 1]], - "", self::ERROR, ], + '', self::ERROR, ], // pattern [['format' => 'default', 'constraints' => ['pattern' => '^[a-z]*$']], 'AAA', self::ERROR], - [['format' => 'default', 'constraints' => ['pattern' => '^[a-z]*$']], 'aaa', 'aaa'] + [['format' => 'default', 'constraints' => ['pattern' => '^[a-z]*$']], 'aaa', 'aaa'], ]); } @@ -561,13 +561,13 @@ protected function assertFieldTestData($fieldType, $testData) list($format, $inputValue, $expectedCastValue, $expectedInferType) = $testLine; if (is_array($format)) { $descriptor = $format; - $descriptor["type"] = $fieldType; + $descriptor['type'] = $fieldType; } else { $descriptor = ['type' => $fieldType, 'format' => $format]; } $assertMessage = 'descriptor='.json_encode($descriptor).", input='".json_encode($inputValue)."', expected='".json_encode($expectedCastValue)."'"; - if (!isset($descriptor["name"])) { - $descriptor["name"] = 'unknown'; + if (!isset($descriptor['name'])) { + $descriptor['name'] = 'unknown'; } $field = FieldsFactory::field($descriptor); if ($expectedCastValue === self::ERROR) { From 4c5c4c01224327e4de30f87d0755fa62baa20efa Mon Sep 17 00:00:00 2001 From: Ori Hoch Date: Thu, 13 Jul 2017 15:43:41 +0300 Subject: [PATCH 3/4] minor fixes for compliancy --- README.md | 127 ++++++++++++++++++++++------------- src/Fields/BaseField.php | 15 +++++ src/Fields/FieldsFactory.php | 2 +- src/Fields/GeojsonField.php | 4 ++ src/Fields/GeopointField.php | 40 +++++------ tests/FieldTest.php | 17 +++++ tests/FieldTypesTest.php | 22 +++--- 7 files changed, 149 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index 2d703fe..ddc65a1 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,58 @@ A utility library for working with [Table Schema](https://specs.frictionlessdata $ composer require frictionlessdata/tableschema ``` +### Table + +Table class allows to iterate over data conforming to a table schema + +Instantiate a Table object based on a data source and a table schema. + +```php +use frictionlessdata\tableschema\Table; + +$table = new Table("tests/fixtures/data.csv", ["fields" => [ + ["name" => "first_name"], + ["name" => "last_name"], + ["name" => "order"] +]]); +``` + +Schema can be any parameter valid for the Schema object (See below), so you can use a url or filename which contains the schema + +```php +$table = new Table("tests/fixtures/data.csv", "tests/fixtures/data.json"); +``` + +iterate over the data, all the values are cast and validated according to the schema + +```php +foreach ($table as $row) { + print($row["order"]." ".$row["first_name"]." ".$row["last_name"]."\n"); +}; +``` + +validate function will validate the schema and get some sample of the data itself to validate it as well + +```php +Table::validate(new CsvDataSource("http://invalid.data.source/"), $schema); +``` + +You can instantiate a table object without schema, in this case the schema will be inferred automatically based on the data + +```php +$table = new Table("tests/fixtures/data.csv"); +$table->schema()->fields(); // ["first_name" => StringField, "last_name" => StringField, "order" => IntegerField] +``` + +Additional methods and functionality + +```php +$table->headers() // ["first_name", "last_name", "order"] +$table->save("output.csv") // iterate over all the rows and save the to a csv file +$table->schema() // get the Schema object +$table->read() // returns all the data as an array +``` + ### Schema Schema class provides helpful methods for working with a table schema and related data. @@ -26,7 +78,7 @@ Schema class provides helpful methods for working with a table schema and relate Schema objects can be constructed using any of the following: -* php array +* php array (or object) ```php $schema = new Schema([ 'fields' => [ @@ -68,14 +120,7 @@ $schema->missingValues(); // [""] $schema->primaryKey(); // ["id"] $schema->foreignKeys(); // [] $schema->fields(); // ["id" => IntegerField, "name" => StringField] -$field = $schema->field("id"); -$field("id")->format(); // "default" -$field("id")->name(); // "id" -$field("id")->type(); // "integer" -$field("id")->constraints(); // (object)["required"=>true, "minimum"=>1, "maximum"=>500] -$field("id")->enum(); // [] -$field("id")->required(); // true -$field("id")->unique(); // false +$field = $schema->field("id"); // Field object (See Field reference below) ``` validate function accepts the same arguemnts as the Schema constructor but returns a list of errors instead of raising exceptions @@ -123,7 +168,7 @@ $schema->fields([ ]); ``` -appropriate field object is created according to the given descriptor +appropriate Field object is created according to the given descriptor (see below for Field class reference) ```php $schema->field("id"); // IntegerField object @@ -145,62 +190,52 @@ $schema->primaryKey(["id"]); after every change - schema is validated and will raise Exception in case of validation errors -finally, save the schema to a json file +Finally, you can get the full validated descriptor ```php -$schema->save("my-schema.json"); +$schema->fullDescriptor(); ``` -### Table - -Table class allows to iterate over data conforming to a table schema - -Instantiate a Table object based on a data source and a table schema. +And, save it to a json file ```php -use frictionlessdata\tableschema\Table; - -$table = new Table("tests/fixtures/data.csv", ["fields" => [ - ["name" => "first_name"], - ["name" => "last_name"], - ["name" => "order"] -]]); +$schema->save("my-schema.json"); ``` -Schema can be any parameter valid for the Schema object, so you can use a url or filename which contains the schema - -```php -$table = new Table("tests/fixtures/data.csv", "tests/fixtures/data.json"); -``` +### Field -iterate over the data, all the values are cast and validated according to the schema +Field class represents a single table schema field descriptor -```php -foreach ($table as $row) { - print($row["order"]." ".$row["first_name"]." ".$row["last_name"]."\n"); -}; -``` +Create a field from a descriptor -validate function will validate the schema and get some sample of the data itself to validate it as well - ```php -Table::validate(new CsvDataSource("http://invalid.data.source/"), $schema); +use frictionlessdata\tableschema\Fields\FieldsFactory; +$field = FieldsFactory::field([ + "name" => "id", "type" => "integer", + "constraints" => ["required" => true, "minimum" => 5] +]); ``` -You can instantiate a table object without schema, in this case the schema will be inferred automatically based on the data +Cast and validate values using the field ```php -$table = new Table("tests/fixtures/data.csv"); -$table->schema()->fields(); // ["first_name" => StringField, "last_name" => StringField, "order" => IntegerField] +$field->castValue("3"); // exception: value is below minimum +$field->castValue("7"); // 7 ``` -Additional methods and functionality +Additional method to access field data ```php -$table->headers() // ["first_name", "last_name", "order"] -$table->save("output.csv") // iterate over all the rows and save the to a csv file -$table->schema() // get the Schema object -$table->read() // returns all the data as an array +$field("id")->format(); // "default" +$field("id")->name(); // "id" +$field("id")->type(); // "integer" +$field("id")->constraints(); // (object)["required"=>true, "minimum"=>1, "maximum"=>500] +$field("id")->enum(); // [] +$field("id")->required(); // true +$field("id")->unique(); // false +$field("id")->title(); // "Id" (or null if not provided in descriptor) +$field("id")->description(); // "The ID" (or null if not provided in descriptor) +$field("id")->rdfType(); // "http://schema.org/Thing" (or null if not provided in descriptor) ``` ## Important Notes diff --git a/src/Fields/BaseField.php b/src/Fields/BaseField.php index 286dc87..6a77317 100644 --- a/src/Fields/BaseField.php +++ b/src/Fields/BaseField.php @@ -31,6 +31,21 @@ public function name() return $this->descriptor()->name; } + public function title() + { + return isset($this->descriptor()->title) ? $this->descriptor()->title : null; + } + + public function description() + { + return isset($this->descriptor()->description) ? $this->descriptor()->description : null; + } + + public function rdfType() + { + return isset($this->descriptor()->rdfType) ? $this->descriptor()->rdfType : null; + } + public function format() { return isset($this->descriptor()->format) ? $this->descriptor()->format : 'default'; diff --git a/src/Fields/FieldsFactory.php b/src/Fields/FieldsFactory.php index 4ef863e..9142a98 100644 --- a/src/Fields/FieldsFactory.php +++ b/src/Fields/FieldsFactory.php @@ -38,7 +38,7 @@ class FieldsFactory /** * get a new field object in the correct type according to the descriptor. * - * @param object $descriptor + * @param object|array $descriptor * * @return BaseField * diff --git a/src/Fields/GeojsonField.php b/src/Fields/GeojsonField.php index 8365123..d1de10a 100644 --- a/src/Fields/GeojsonField.php +++ b/src/Fields/GeojsonField.php @@ -12,7 +12,11 @@ protected function validateCastValue($val) } catch (\Exception $e) { throw $this->getValidationException($e->getMessage(), $val); } + if (!$val) { + throw $this->getValidationException('failed to decode json', $val); + } } + $val = json_decode(json_encode($val)); if (!is_object($val)) { throw $this->getValidationException('must be an object', $val); } diff --git a/src/Fields/GeopointField.php b/src/Fields/GeopointField.php index d9d8812..6c24363 100644 --- a/src/Fields/GeopointField.php +++ b/src/Fields/GeopointField.php @@ -18,21 +18,25 @@ protected function validateCastValue($val) if (!is_string($val)) { throw $this->getValidationException('value must be a string', $val); } else { - return $this->getNativeGeopoint(explode(',', $val)); + $val = explode(',', $val); + if (count($val) != 2) { + throw $this->getValidationException('value must be a string with 2 comma-separated elements', $val); + } else { + return $this->getNativeGeopoint($val); + } } case 'array': - if (!is_array($val)) { - throw $this->getValidationException('value must be an array', $val); + if (!is_array($val) || array_keys($val) != [0, 1]) { + throw $this->getValidationException('value must be an array with 2 elements', $val); } else { return $this->getNativeGeopoint($val); } case 'object': - if (!is_object($val)) { - throw $this->getValidationException('value must be an object', $val); - } elseif (!isset($val->lat) || !isset($val->lon)) { + $val = json_decode(json_encode($val), true); + if (!is_array($val) || !array_key_exists('lat', $val) || !array_key_exists('lon', $val)) { throw $this->getValidationException('object must contain lon and lat attributes', $val); } else { - return $this->getNativeGeopoint([$val->lon, $val->lat]); + return $this->getNativeGeopoint([$val['lon'], $val['lat']]); } default: throw $this->getValidationException('invalid format', $val); @@ -46,20 +50,16 @@ public static function type() protected function getNativeGeopoint($arr) { - if (count($arr) != 2) { - throw $this->getValidationException('lon,lat array must contain only lon,lat', json_encode($arr)); + list($lon, $lat) = $arr; + $lon = (int) $lon; + $lat = (int) $lat; + if ( + $lon > 180 || $lon < -180 + || $lat > 90 or $lat < -90 + ) { + throw $this->getValidationException('invalid lon,lat values', json_encode($arr)); } else { - list($lon, $lat) = $arr; - $lon = (int) $lon; - $lat = (int) $lat; - if ( - $lon > 180 || $lon < -180 - || $lat > 90 or $lat < -90 - ) { - throw $this->getValidationException('invalid lon,lat values', json_encode($arr)); - } else { - return [$lon, $lat]; - } + return [$lon, $lat]; } } } diff --git a/tests/FieldTest.php b/tests/FieldTest.php index 4bb22a4..8a65b20 100644 --- a/tests/FieldTest.php +++ b/tests/FieldTest.php @@ -86,6 +86,23 @@ public function testCastValue() $this->assertEquals(1, FieldsFactory::field($this->DESCRIPTOR_MAX)->castValue('1')); } + public function testAdditionalMethods() + { + $field = FieldsFactory::field(['name' => 'name', 'type' => 'string']); + $this->assertEquals(null, $field->title()); + $this->assertEquals(null, $field->description()); + $this->assertEquals(null, $field->rdfType()); + $field = FieldsFactory::field([ + 'name' => 'name', 'type' => 'string', + 'title' => 'Title', + 'description' => 'Description', + 'rdfType' => 'http://schema.org/Thing', + ]); + $this->assertEquals('Title', $field->title()); + $this->assertEquals('Description', $field->description()); + $this->assertEquals('http://schema.org/Thing', $field->rdfType()); + } + public function testCastValueConstraintError() { try { diff --git a/tests/FieldTypesTest.php b/tests/FieldTypesTest.php index 877be04..58ce8da 100644 --- a/tests/FieldTypesTest.php +++ b/tests/FieldTypesTest.php @@ -227,17 +227,17 @@ public function testGeojson() [ 'default', ['properties' => ['Ã' => 'Ã'], 'type' => 'Feature', 'geometry' => null], - ['properties' => ['Ã' => 'Ã'], 'type' => 'Feature', 'geometry' => null], + (object) ['properties' => (object) ['Ã' => 'Ã'], 'type' => 'Feature', 'geometry' => null], ], [ 'default', '{"geometry": null, "type": "Feature", "properties": {"\\u00c3": "\\u00c3"}}', - ['properties' => ['Ã' => 'Ã'], 'type' => 'Feature', 'geometry' => null], + (object) ['properties' => (object) ['Ã' => 'Ã'], 'type' => 'Feature', 'geometry' => null], ], [ 'default', ['coordinates' => [0, 0, 0], 'type' => 'Point'], - ['coordinates' => [0, 0, 0], 'type' => 'Point'], + (object) ['coordinates' => [0, 0, 0], 'type' => 'Point'], ], ['default', 'string', self::ERROR], ['default', 1, self::ERROR], @@ -248,12 +248,12 @@ public function testGeojson() [ 'topojson', ['type' => 'LineString', 'arcs' => [42]], - ['type' => 'LineString', 'arcs' => [42]], + (object) ['type' => 'LineString', 'arcs' => [42]], ], [ 'topojson', '{"type": "LineString", "arcs": [42]}', - ['type' => 'LineString', 'arcs' => [42]], + (object) ['type' => 'LineString', 'arcs' => [42]], ], ['topojson', 'string', self::ERROR], ['topojson', 1, self::ERROR], @@ -264,7 +264,7 @@ public function testGeojson() // enum [[ 'format' => 'default', 'constraints' => ['enum' => ['{"geometry": null, "type": "Feature", "properties": {"\\u00c3": "\\u00c3"}}']], - ], '{"geometry": null, "type": "Feature", "properties": {"\\u00c3": "\\u00c3"}}', ['properties' => ['Ã' => 'Ã'], 'type' => 'Feature', 'geometry' => null]], + ], '{"geometry": null, "type": "Feature", "properties": {"\\u00c3": "\\u00c3"}}', (object) ['properties' => (object) ['Ã' => 'Ã'], 'type' => 'Feature', 'geometry' => null]], [[ 'format' => 'default', 'constraints' => ['enum' => ['{"geometry": null, "type": "Feature", "properties": {"\\u00c3": "\\u00c3"}}']], ], '{"geometry": null, "type": "Feature", "properties": {"\\u00c3": "\\u00c4"}}', self::ERROR], @@ -387,10 +387,10 @@ public function testNumber() public function testObject() { $this->assertFieldTestData('object', [ - ['default', [], []], - ['default', '{}', []], - ['default', ['key' => 'value'], ['key' => 'value']], - ['default', '{"key": "value"}', ['key' => 'value']], + ['default', (object) [], (object) []], + ['default', '{}', (object) []], + ['default', (object) ['key' => 'value'], (object) ['key' => 'value']], + ['default', '{"key": "value"}', (object) ['key' => 'value']], ['default', '["key", "value"]', self::ERROR], ['default', 'string', self::ERROR], ['default', 1, self::ERROR], @@ -399,7 +399,7 @@ public function testObject() // required [['format' => 'default', 'constraints' => ['required' => true]], null, self::ERROR], // enum - [['format' => 'default', 'constraints' => ['enum' => ['{"foo":"bar"}']]], '{"foo":"bar"}', ['foo' => 'bar']], + [['format' => 'default', 'constraints' => ['enum' => ['{"foo":"bar"}']]], '{"foo":"bar"}', (object) ['foo' => 'bar']], [['format' => 'default', 'constraints' => ['enum' => ['{"foo":"bar"}']]], '{"foox":"bar"}', self::ERROR], // minLength / maxLength [['format' => 'default', 'constraints' => ['minLength' => 1]], '{}', self::ERROR], From 5bdc84d664e8aa4c351a0868f442674a087f5908 Mon Sep 17 00:00:00 2001 From: Ori Hoch Date: Thu, 13 Jul 2017 15:50:36 +0300 Subject: [PATCH 4/4] fixes #5: temporary files should be deleted during unit tests --- tests/SchemaTest.php | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/tests/SchemaTest.php b/tests/SchemaTest.php index 72dda26..6b9a41e 100644 --- a/tests/SchemaTest.php +++ b/tests/SchemaTest.php @@ -127,8 +127,8 @@ public function testConstructFromInvalidResource() public function testDifferentValidDescriptorSources() { - $simpleFile = tempnam(sys_get_temp_dir(), 'tableschema-php-tests'); - $fullFile = tempnam(sys_get_temp_dir(), 'tableschema-php-tests'); + $simpleFile = $this->getTempFile(); + $fullFile = $this->getTempFile(); file_put_contents($simpleFile, json_encode($this->simpleDescriptor)); file_put_contents($fullFile, json_encode($this->fullDescriptor)); $descriptors = [ @@ -504,7 +504,7 @@ public function testSchemaToEditableSchema() public function testSave() { $schema = new Schema($this->minDescriptorJson); - $filename = tempnam(sys_get_temp_dir(), 'tableschema-php-tests'); + $filename = $this->getTempFile(); $schema->save($filename); $this->assertEquals($schema->fullDescriptor(), json_decode(file_get_contents($filename))); } @@ -532,6 +532,17 @@ public function testSpecsUriFormat() ]], $validator->getErrors()); } + public function tearDown() + { + foreach ($this->tempFiles as $tempFile) { + if (file_exists($tempFile)) { + unlink($tempFile); + } + } + } + + protected $tempFiles = []; + protected function assertValidationErrors($expectedValidationErrors, $descriptor) { $this->assertEquals( @@ -570,4 +581,12 @@ protected function assertCastRowException($expectedError, $descriptor, $inputRow $this->assertEquals($expectedError, $e->getMessage()); } } + + protected function getTempFile() + { + $file = tempnam(sys_get_temp_dir(), 'tableschema-php-tests'); + $this->tempFiles[] = $file; + + return $file; + } }