Skip to content

Commit 03a20a9

Browse files
authored
Merge pull request #179 from Planetbiru/feature/version-3.22.2
Feature/version 3.22.2
2 parents 745e7d0 + abbc189 commit 03a20a9

17 files changed

Lines changed: 184 additions & 141 deletions

src/Constants/PicoHttpStatus.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<?php
2+
23
namespace MagicObject\Constants;
34

45
/**

src/DataLabel/PicoDataLabel.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
class PicoDataLabel extends SetterGetter
2020
{
2121
const ANNOTATION_PROPERTIES = "Properties"; // Annotation key for properties
22-
const ANNOTATION_TABLE = "Table"; // Annotation key for table name
23-
const KEY_NAME = "name"; // Key for the name
24-
const ANNOTATION_VAR = "var"; // Annotation key for variable
22+
const ANNOTATION_TABLE = "Table"; // Annotation key for table name
23+
const KEY_NAME = "name"; // Key for the name
24+
const ANNOTATION_VAR = "var"; // Annotation key for variable
2525

2626
/**
2727
* Parameters defined in the class annotations.

src/DataTable.php

Lines changed: 69 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -338,77 +338,119 @@ private function label($reflexProp, $parameters, $key, $defaultLabel)
338338
}
339339

340340
/**
341-
* Appends table rows based on class properties.
342-
*
343-
* This method generates rows for the table based on the properties of the class
344-
* and appends them to the provided DOM node.
341+
* Appends table rows based on class properties using reflection and annotations.
345342
*
346343
* @param DOMDocument $doc The DOM document used to create elements.
347344
* @param DOMNode $tbody The DOM node representing the <tbody> of the table.
348-
* @param array $props Array of ReflectionProperty objects representing class properties.
345+
* @param ReflectionProperty[] $props Array of ReflectionProperty objects.
349346
* @param string $className Name of the class for reflection.
350347
* @return self Returns the current instance for method chaining.
351348
*/
352349
private function appendByProp($doc, $tbody, $props, $className)
353350
{
354351
foreach ($props as $prop) {
355352
$key = $prop->name;
356-
$label = $key;
357353
$value = $this->get($key);
358-
if (is_scalar($value)) {
359-
$tr = $tbody->appendChild($doc->createElement(self::TAG_TR));
360-
361-
$reflexProp = new PicoAnnotationParser($className, $key, PicoAnnotationParser::PROPERTY);
362354

363-
if ($reflexProp != null) {
364-
$parameters = $reflexProp->getParametersAsObject();
365-
if ($parameters->issetLabel()) {
366-
$label = $this->label($reflexProp, $parameters, $key, $label);
355+
// Inclusion of null values often necessary for table structure consistency
356+
if (is_scalar($value) || $value === null) {
357+
$label = $key;
358+
359+
// Parse annotations for custom label
360+
try {
361+
$reflexProp = new PicoAnnotationParser($className, $key, PicoAnnotationParser::PROPERTY);
362+
if ($reflexProp != null) {
363+
$parameters = $reflexProp->getParametersAsObject();
364+
// Assuming issetLabel() or similar check exists in your annotation parser
365+
if (method_exists($parameters, 'issetLabel') && $parameters->issetLabel()) {
366+
$label = $this->label($reflexProp, $parameters, $key, $label);
367+
}
367368
}
369+
} catch (Exception $e) {
370+
// Fallback to property name if annotation parsing fails
371+
$label = $key;
368372
}
369373

370-
$td1 = $tr->appendChild($doc->createElement(self::TAG_TD));
374+
// Create Row
375+
$tr = $doc->createElement(self::TAG_TR);
376+
$tbody->appendChild($tr);
377+
378+
// Column 1: Label
379+
$td1 = $doc->createElement(self::TAG_TD);
371380
$td1->setAttribute(self::KEY_CLASS, self::TD_LABEL);
372-
$td1->textContent = $label;
381+
$td1->appendChild($doc->createTextNode($label));
382+
$tr->appendChild($td1);
373383

374-
$td2 = $tr->appendChild($doc->createElement(self::TAG_TD));
384+
// Column 2: Value
385+
$td2 = $doc->createElement(self::TAG_TD);
375386
$td2->setAttribute(self::KEY_CLASS, self::TD_VALUE);
376-
$td2->textContent = isset($value) ? $value : "";
387+
388+
// Safe formatting for boolean/null/strings
389+
$displayValue = $this->formatScalarValue($value);
390+
$td2->appendChild($doc->createTextNode($displayValue));
391+
$tr->appendChild($td2);
377392
}
378393
}
379394
return $this;
380395
}
381396

382-
/**
397+
/**
383398
* Appends table rows based on provided values.
384399
*
385400
* This method takes an array of values and creates rows in the table,
386401
* appending them to the provided DOM node.
387402
*
388403
* @param DOMDocument $doc The DOM document used to create elements.
389404
* @param DOMNode $tbody The DOM node representing the <tbody> of the table.
390-
* @param stdClass $values Data to append as rows.
405+
* @param array|stdClass $values Data to append as rows.
391406
* @return self Returns the current instance for method chaining.
392407
*/
393408
private function appendByValues($doc, $tbody, $values)
394409
{
410+
if (empty($values)) {
411+
return $this;
412+
}
413+
395414
foreach ($values as $propertyName => $value) {
396-
if (is_scalar($value)) {
397-
$tr = $tbody->appendChild($doc->createElement(self::TAG_TR));
398-
$label = $this->getLabel($propertyName);
415+
// Check if value can be displayed as string
416+
if (is_scalar($value) || $value === null) {
417+
$tr = $doc->createElement(self::TAG_TR);
418+
$tbody->appendChild($tr);
399419

400-
$td1 = $tr->appendChild($doc->createElement(self::TAG_TD));
420+
$label = $this->getLabel($propertyName);
421+
422+
// Column 1: Label
423+
$td1 = $doc->createElement(self::TAG_TD);
401424
$td1->setAttribute(self::KEY_CLASS, self::TD_LABEL);
402-
$td1->textContent = $label;
425+
// Use createTextNode for safer character handling
426+
$td1->appendChild($doc->createTextNode($label));
427+
$tr->appendChild($td1);
403428

404-
$td2 = $tr->appendChild($doc->createElement(self::TAG_TD));
429+
// Column 2: Value
430+
$td2 = $doc->createElement(self::TAG_TD);
405431
$td2->setAttribute(self::KEY_CLASS, self::TD_VALUE);
406-
$td2->textContent = isset($value) ? $value : "";
432+
433+
// Format boolean or null if necessary
434+
$displayValue = $this->formatScalarValue($value);
435+
$td2->appendChild($doc->createTextNode($displayValue));
436+
$tr->appendChild($td2);
407437
}
408438
}
409439
return $this;
410440
}
411441

442+
/**
443+
* Helper to ensure value is safe string for DOM
444+
* @param mixed $value
445+
* @return string
446+
*/
447+
private function formatScalarValue($value)
448+
{
449+
if ($value === true) return 'true';
450+
if ($value === false) return 'false';
451+
return (string)(isset($value) ? $value : "");
452+
}
453+
412454
/**
413455
* Gets the label for a specified property.
414456
*

src/Database/PicoDatabasePersistence.php

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ class PicoDatabasePersistence // NOSONAR
8585
const COMMA_RETURN = ", \r\n";
8686
const INLINE_TRIM = " \r\n\t ";
8787
const ALWAYS_TRUE = "(1=1)";
88+
89+
const IS_NULL = " is null";
90+
const CLAUSE_AND = " and ";
8891

8992
/**
9093
* Database connection
@@ -929,7 +932,7 @@ private function getWhere($info, $queryBuilder)
929932
$value = $queryBuilder->escapeValue($value);
930933
if(strcasecmp($value, self::KEY_NULL) == 0)
931934
{
932-
$wheres[] = $columnName . " is null";
935+
$wheres[] = $columnName . self::IS_NULL;
933936
}
934937
else
935938
{
@@ -940,7 +943,7 @@ private function getWhere($info, $queryBuilder)
940943
{
941944
throw new NoPrimaryKeyDefinedException("No primary key defined");
942945
}
943-
return implode(" and ", $wheres);
946+
return implode(self::CLAUSE_AND, $wheres);
944947
}
945948

946949
/**
@@ -977,7 +980,7 @@ private function getWhereWithColumns($info, $queryBuilder)
977980
$escapedValue = $queryBuilder->escapeValue($value);
978981
if(strcasecmp($escapedValue, self::KEY_NULL) == 0)
979982
{
980-
$wheres[] = $columnName . " is null";
983+
$wheres[] = $columnName . self::IS_NULL;
981984
$columns[$columnName] = null;
982985
}
983986
else
@@ -992,7 +995,7 @@ private function getWhereWithColumns($info, $queryBuilder)
992995
}
993996

994997
$result->columns = $columns;
995-
$result->whereClause = implode(" and ", $wheres);
998+
$result->whereClause = implode(self::CLAUSE_AND, $wheres);
996999
return $result;
9971000
}
9981001

@@ -2150,14 +2153,14 @@ private function createWhereByPrimaryKeys($queryBuilder, $primaryKeys, $property
21502153
$columnValue = $propertyValues[$index];
21512154
if($columnValue === null)
21522155
{
2153-
$wheres[] = $columnName . " is null";
2156+
$wheres[] = $columnName . self::IS_NULL;
21542157
}
21552158
else
21562159
{
21572160
$wheres[] = $columnName . " = " . $queryBuilder->escapeValue($propertyValues[$index]);
21582161
}
21592162
}
2160-
$where = implode(" and ", $wheres);
2163+
$where = implode(self::CLAUSE_AND, $wheres);
21612164
if(!$this->isValidFilter($where))
21622165
{
21632166
throw new InvalidFilterException(self::MESSAGE_INVALID_FILTER);

src/Database/PicoPageData.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ public function getResultAsArray()
303303
* @param mixed $data The data to convert. Can be a MagicObject, an array, an object, or a scalar value.
304304
* @return mixed The converted data as a plain PHP array, stdClass object, or scalar value.
305305
*/
306-
protected function magicObjectToArray($data)
306+
protected function magicObjectToArray($data) // NOSONAR
307307
{
308308
// Null or scalar
309309
if (is_null($data) || is_scalar($data)) {
@@ -363,8 +363,6 @@ protected function magicObjectToArray($data)
363363
return $data;
364364
}
365365

366-
367-
368366
/**
369367
* Get the current page number in the pagination context.
370368
*

src/Database/PicoSpecificationFilter.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@
1414
*/
1515
class PicoSpecificationFilter
1616
{
17-
const DATA_TYPE_NUMBER = "number";
18-
const DATA_TYPE_STRING = "string";
19-
const DATA_TYPE_BOOLEAN = "boolean";
20-
const DATA_TYPE_ARRAY_NUMBER = "number[]";
21-
const DATA_TYPE_ARRAY_STRING = "string[]";
17+
const DATA_TYPE_NUMBER = "number";
18+
const DATA_TYPE_STRING = "string";
19+
const DATA_TYPE_BOOLEAN = "boolean";
20+
const DATA_TYPE_ARRAY_NUMBER = "number[]";
21+
const DATA_TYPE_ARRAY_STRING = "string[]";
2222
const DATA_TYPE_ARRAY_BOOLEAN = "boolean[]";
23-
const DATA_TYPE_FULLTEXT = "fulltext";
24-
const DATA_TYPE_TEXT_EQUALS = "textequals";
23+
const DATA_TYPE_FULLTEXT = "fulltext";
24+
const DATA_TYPE_TEXT_EQUALS = "textequals";
2525

2626
/**
2727
* The name of the column this filter applies to.

src/Database/PicoTableInfoExtended.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
*/
1616
class PicoTableInfoExtended extends PicoTableInfo
1717
{
18-
const NAME = "name"; // Key for the column name
18+
const NAME = "name"; // Key for the column name
1919
const PREV_NAME = "prevColumnName"; // Key for the previous column name
20-
const ELEMENT = "element"; // Key for the element
20+
const ELEMENT = "element"; // Key for the element
2121

2222
/**
2323
* Gets an instance of PicoTableInfoExtended.

src/Generator/PicoDatabaseDump.php

Lines changed: 57 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -48,24 +48,18 @@ class PicoDatabaseDump // NOSONAR
4848
protected $columns = array();
4949

5050
/**
51-
* Generates a SQL CREATE TABLE statement based on the provided entity schema.
52-
* * This method detects the database type and utilizes the appropriate utility
53-
* class to format columns, primary keys, and auto-increment constraints.
54-
* It supports MySQL, MariaDB, PostgreSQL, SQLite, and SQL Server.
51+
* Instantiates the appropriate database dump utility based on the database type.
5552
*
56-
* @param array $entity The entity schema containing 'name' and 'columns' (an array of column definitions).
57-
* @param string $databaseType The type of database (e.g., PicoDatabaseType::DATABASE_TYPE_MARIADB).
58-
* @param bool $createIfNotExists Whether to add the "IF NOT EXISTS" clause to the CREATE statement.
59-
* @param bool $dropIfExists Whether to prepend a commented-out "DROP TABLE IF EXISTS" statement.
60-
* @param string $engine The storage engine to use (default is 'InnoDB', primarily for MySQL/MariaDB).
61-
* @param string $charset The character set for the table (default is 'utf8mb4').
62-
* * @return string The generated SQL DDL statement or an empty string if the database type is unsupported.
53+
* This factory method maps specific database engines to their respective
54+
* utility classes (MySQL, PostgreSQL, SQLite, or SQL Server) to handle
55+
* database-specific dumping operations.
56+
*
57+
* @param string $databaseType The type of database (e.g., MySQL, PostgreSQL, SQLite).
58+
* @return PicoDatabaseUtilBase|string Returns an instance of the database utility tool
59+
* or an empty string if the database type is not supported.
6360
*/
64-
public function dumpStructureFromSchema($entity, $databaseType, $createIfNotExists = false, $dropIfExists = false, $engine = 'InnoDB', $charset = 'utf8mb4')
61+
private function getDatabaseDumpTool($databaseType)
6562
{
66-
$tableName = $entity['name'];
67-
68-
// 1. Initialize Tool based on Database Type
6963
switch ($databaseType) {
7064
case PicoDatabaseType::DATABASE_TYPE_MARIADB:
7165
case PicoDatabaseType::DATABASE_TYPE_MYSQL:
@@ -84,6 +78,29 @@ public function dumpStructureFromSchema($entity, $databaseType, $createIfNotExis
8478
default:
8579
return "";
8680
}
81+
return $tool;
82+
}
83+
84+
/**
85+
* Generates a SQL CREATE TABLE statement based on the provided entity schema.
86+
* * This method detects the database type and utilizes the appropriate utility
87+
* class to format columns, primary keys, and auto-increment constraints.
88+
* It supports MySQL, MariaDB, PostgreSQL, SQLite, and SQL Server.
89+
*
90+
* @param array $entity The entity schema containing 'name' and 'columns' (an array of column definitions).
91+
* @param string $databaseType The type of database (e.g., PicoDatabaseType::DATABASE_TYPE_MARIADB).
92+
* @param bool $createIfNotExists Whether to add the "IF NOT EXISTS" clause to the CREATE statement.
93+
* @param bool $dropIfExists Whether to prepend a commented-out "DROP TABLE IF EXISTS" statement.
94+
* @param string $engine The storage engine to use (default is 'InnoDB', primarily for MySQL/MariaDB).
95+
* @param string $charset The character set for the table (default is 'utf8mb4').
96+
* @return string The generated SQL DDL statement or an empty string if the database type is unsupported.
97+
*/
98+
public function dumpStructureFromSchema($entity, $databaseType, $createIfNotExists = false, $dropIfExists = false, $engine = 'InnoDB', $charset = 'utf8mb4')
99+
{
100+
$tableName = $entity['name'];
101+
102+
// 1. Initialize Tool based on Database Type
103+
$tool = $this->getDatabaseDumpTool($databaseType);
87104

88105
$columns = array();
89106
$primaryKeys = array();
@@ -154,15 +171,11 @@ public function dumpDataFromSchema($entity, $databaseType, $batchSize = 100)
154171
{
155172
// Check if the target database is PostgreSQL
156173
$isPgSql = $databaseType == PicoDatabaseType::DATABASE_TYPE_PGSQL || $databaseType == PicoDatabaseType::DATABASE_TYPE_POSTGRESQL;
157-
$columnInfo = array();
174+
158175
$tableName = $entity['name'];
159176

160177
// 1. Prepare Column Information for type-casting
161-
if (isset($entity['columns']) && is_array($entity['columns'])) {
162-
foreach ($entity['columns'] as $column) {
163-
$columnInfo[$column['name']] = $this->getColumnInfo($column);
164-
}
165-
}
178+
$columnInfo = $this->prepareColumnInfo($entity);
166179

167180
$validColumnNames = array_keys($columnInfo);
168181

@@ -203,12 +216,34 @@ public function dumpDataFromSchema($entity, $databaseType, $batchSize = 100)
203216
. implode(",\r\n", $rows)
204217
. ";\r\n\r\n";
205218
}
206-
207219
}
208220

209221
return $allSql;
210222
}
211223

224+
/**
225+
* Prepares and normalizes column metadata from the entity schema.
226+
*
227+
* This method iterates through the column definitions of an entity and
228+
* transforms them into a structured associative array of column information,
229+
* indexed by the column names.
230+
*
231+
* @param array $entity The entity schema containing the 'columns' definition.
232+
* @return array<string, \stdClass> An associative array where keys are column names
233+
* and values are column metadata objects.
234+
*/
235+
private function prepareColumnInfo($entity)
236+
{
237+
$columnInfo = array();
238+
if (isset($entity['columns']) && is_array($entity['columns'])) {
239+
foreach ($entity['columns'] as $column) {
240+
// Assuming getColumnInfo returns an object/stdClass based on previous code
241+
$columnInfo[$column['name']] = $this->getColumnInfo($column);
242+
}
243+
}
244+
return $columnInfo;
245+
}
246+
212247
/**
213248
* Formats raw data values based on column metadata and database requirements.
214249
*

0 commit comments

Comments
 (0)