create-prisma-php-app 1.27.4 → 1.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -81,6 +81,12 @@ final class Validator
81
81
  */
82
82
  public static function cuid($value): ?string
83
83
  {
84
+ // Ensure the value is a string
85
+ if (!is_string($value)) {
86
+ return null;
87
+ }
88
+
89
+ // Perform the CUID validation
84
90
  return preg_match('/^c[0-9a-z]{24}$/', $value) ? $value : null;
85
91
  }
86
92
 
@@ -92,6 +98,12 @@ final class Validator
92
98
  */
93
99
  public static function cuid2($value): ?string
94
100
  {
101
+ // Ensure the value is a string
102
+ if (!is_string($value)) {
103
+ return null;
104
+ }
105
+
106
+ // Perform the CUID2 validation
95
107
  return preg_match('/^[0-9a-zA-Z_-]{21,}$/', $value) ? $value : null;
96
108
  }
97
109
 
@@ -179,15 +191,21 @@ final class Validator
179
191
  }
180
192
 
181
193
  /**
182
- * Validate a datetime in a given format.
194
+ * Validate and format a datetime value in a given format or ISO 8601 by default.
183
195
  *
184
196
  * @param mixed $value The value to validate.
185
- * @param string $format The datetime format.
186
- * @return string|null The valid datetime string or null if invalid.
197
+ * @param string $format The desired datetime format (default is database-friendly 'Y-m-d H:i:s.u').
198
+ * @return string|null The valid datetime string for the database or null if invalid.
187
199
  */
188
- public static function dateTime($value, string $format = 'Y-m-d H:i:s'): ?string
200
+ public static function dateTime($value, string $format = 'Y-m-d H:i:s.u'): ?string
189
201
  {
190
- return self::date($value, $format);
202
+ try {
203
+ $date = new \DateTime($value);
204
+ } catch (\Exception) {
205
+ return null;
206
+ }
207
+
208
+ return $date->format($format);
191
209
  }
192
210
 
193
211
  // Boolean Validation
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-prisma-php-app",
3
- "version": "1.27.4",
3
+ "version": "1.28.0",
4
4
  "description": "Prisma-PHP: A Revolutionary Library Bridging PHP with Prisma ORM",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -1,406 +0,0 @@
1
- <?php
2
-
3
- namespace Lib\Prisma\Classes;
4
-
5
- use Lib\Validator;
6
-
7
- enum ArrayType: string
8
- {
9
- case Associative = 'associative';
10
- case Indexed = 'indexed';
11
- case Value = 'value';
12
- }
13
-
14
- final class Utility
15
- {
16
- public static function checkFieldsExistWithReferences(
17
- array $select,
18
- array &$relatedEntityFields,
19
- array &$primaryEntityFields,
20
- array $relationName,
21
- array $fields,
22
- string $modelName,
23
- string $timestamp
24
- ) {
25
- if (isset($select) && is_array($select)) {
26
- foreach ($select as $key => $value) {
27
- if ($key === $timestamp) continue;
28
-
29
- if (is_numeric($key) && is_string($value)) {
30
- if (array_key_exists($value, $fields))
31
- throw new \Exception("The '$value' is indexed, waiting example: ['$value' => true]");
32
- }
33
-
34
- if (isset($value) && empty($value) || !is_bool($value)) {
35
- if (is_string($key) && !array_key_exists($key, $fields)) {
36
- throw new \Exception("The field '$key' does not exist in the $modelName model.");
37
- }
38
-
39
- if (is_string($key) && array_key_exists($key, $fields)) {
40
- if (!is_bool($value) && !is_array($value)) {
41
- throw new \Exception("The '$key' is indexed, waiting example: ['$key' => true]");
42
- }
43
- }
44
-
45
- if (!is_array($value))
46
- continue;
47
- }
48
-
49
- if (is_string($key) && is_array($value)) {
50
- if (isset($value['select'])) {
51
- $relatedEntityFields[$key] = $value['select'];
52
- } else {
53
- if (is_array($value) && empty($value)) {
54
- $relatedEntityFields[$key] = [$key];
55
- } else {
56
- if (!is_bool($value) || empty($value)) {
57
- throw new \Exception("The '$key' is indexed, waiting example: ['$key' => true] or ['$key' => ['select' => ['field1' => true, 'field2' => true]]]");
58
- }
59
- }
60
- }
61
- } else {
62
- foreach (explode(',', $key) as $fieldName) {
63
- if ($key === $timestamp || $fieldName === $timestamp) continue;
64
- $fieldName = trim($fieldName);
65
-
66
- if (!array_key_exists($fieldName, $fields)) {
67
- $availableFields = implode(', ', array_keys($fields));
68
- throw new \Exception("The field '$fieldName' does not exist in the $modelName model. Available fields are: $availableFields");
69
- }
70
-
71
- if (
72
- in_array($fieldName, $relationName) ||
73
- (isset($fields[$fieldName]) && in_array($fields[$fieldName]['type'], $relationName))
74
- ) {
75
- $relatedEntityFields[$fieldName] = [$fieldName];
76
- continue;
77
- }
78
-
79
- $isRelationalOrInverse = false;
80
- if (isset($fields[$fieldName]['decorators'])) {
81
- foreach ($fields[$fieldName]['decorators'] as $decoratorKey => $decoratorValue) {
82
- if ($decoratorKey === 'relation' || $decoratorKey === 'inverseRelation') {
83
- $isRelationalOrInverse = true;
84
- break;
85
- }
86
- }
87
- }
88
-
89
- if (!$isRelationalOrInverse) {
90
- if (in_array($fieldName, $primaryEntityFields)) continue;
91
- $primaryEntityFields[] = $fieldName;
92
- }
93
- }
94
- }
95
- }
96
- }
97
- }
98
-
99
- public static function checkFieldsExist(array $select, array $fields, string $modelName)
100
- {
101
- foreach ($select as $key => $value) {
102
- if (is_numeric($key) && is_string($value)) {
103
- if (array_key_exists($value, $fields))
104
- throw new \Exception("The '$value' is indexed, waiting example: ['$value' => true]");
105
- }
106
-
107
- if (isset($value) && empty($value) || !is_bool($value)) {
108
- if (is_string($key) && !array_key_exists($key, $fields)) {
109
- throw new \Exception("The field '$key' does not exist in the $modelName model.");
110
- }
111
-
112
- if (is_array($value) && !empty($value)) {
113
-
114
- $isRelatedModel = false;
115
-
116
- foreach ($fields as $field) {
117
- $relation = $field['decorators']['relation'] ?? null;
118
- $inverseRelation = $field['decorators']['inverseRelation'] ?? null;
119
- $implicitRelation = $field['decorators']['implicitRelation'] ?? null;
120
-
121
- if (isset($relation['name']) && $relation['name'] == $key || isset($inverseRelation['fromField']) && $inverseRelation['fromField'] == $key || isset($implicitRelation['fromField']) && $implicitRelation['fromField'] == $key) $isRelatedModel = true;
122
- }
123
-
124
- if ($isRelatedModel) continue;
125
-
126
- $keys = array_keys($value);
127
- foreach ($keys as $fieldName) {
128
- $fieldName = trim($fieldName);
129
- if (!array_key_exists($fieldName, $fields)) {
130
- throw new \Exception("The field '$fieldName' does not exist in the $modelName model.");
131
- }
132
- }
133
- }
134
-
135
- continue;
136
- }
137
-
138
- foreach (explode(',', $key) as $fieldName) {
139
- $fieldName = trim($fieldName);
140
- if (!array_key_exists($fieldName, $fields)) {
141
- throw new \Exception("The field '$fieldName' does not exist in the $modelName model.");
142
- }
143
- }
144
- }
145
- }
146
-
147
- public static function checkArrayContents(array $array): ArrayType
148
- {
149
- foreach ($array as $key => $value) {
150
- if (is_array($value)) {
151
- if (array_keys($value) !== range(0, count($value) - 1)) {
152
- return ArrayType::Associative;
153
- } else {
154
- return ArrayType::Indexed;
155
- }
156
- } else {
157
- return ArrayType::Value;
158
- }
159
- }
160
- }
161
-
162
- public static function checkIncludes(array $include, array &$relatedEntityFields, array &$includes, array $fields, string $modelName)
163
- {
164
- if (isset($include) && is_array($include)) {
165
- foreach ($include as $key => $value) {
166
- self::processIncludeValue($key, $value, $relatedEntityFields, $fields, $modelName, $key);
167
-
168
- if (is_numeric($key) && is_string($value)) {
169
- throw new \Exception("The '$value' is indexed, waiting example: ['$value' => true]");
170
- }
171
-
172
- if (isset($value) && empty($value) || !is_bool($value)) {
173
- continue;
174
- }
175
-
176
- if (!array_key_exists($key, $fields)) {
177
- throw new \Exception("The field '$key' does not exist in the $modelName model.");
178
- }
179
-
180
- $includes[$key] = $value;
181
- }
182
- }
183
- }
184
-
185
- private static function processIncludeValue($key, $value, &$relatedEntityFields, $fields, $modelName, $parentKey)
186
- {
187
- if (isset($value['select'])) {
188
- $relatedEntityFields[$parentKey] = $value;
189
- } elseif (is_array($value)) {
190
- if (empty($value)) {
191
- $relatedEntityFields[$parentKey] = [$parentKey];
192
- } else {
193
- foreach ($value as $k => $v) {
194
- if (is_string($k) && (is_bool($v) || empty($v))) {
195
- $relatedEntityFields[$parentKey]['include'] = [$k => $v];
196
- } else {
197
- self::processIncludeValue($k, $v, $relatedEntityFields, $fields, $modelName, $parentKey);
198
- }
199
- }
200
- }
201
- } else {
202
- if (!is_bool($value) || empty($value)) {
203
- throw new \Exception("The '$value' is indexed, waiting example: ['$value' => true] or ['$value' => ['select' => ['field1' => true, 'field2' => true]]]");
204
- }
205
- }
206
- }
207
-
208
- public static function processConditions(array $conditions, &$sqlConditions, &$bindings, $dbType, $prefix = '', $level = 0)
209
- {
210
- foreach ($conditions as $key => $value) {
211
- if (in_array($key, ['AND', 'OR', 'NOT'])) {
212
- $groupedConditions = [];
213
- if ($key === 'NOT') {
214
- self::processNotCondition($value, $groupedConditions, $bindings, $dbType, $prefix . $key . '_', $level);
215
- if (!empty($groupedConditions)) {
216
- $conditionGroup = '(' . implode(" $key ", $groupedConditions) . ')';
217
- $conditionGroup = 'NOT ' . $conditionGroup;
218
- $sqlConditions[] = $conditionGroup;
219
- }
220
- } else {
221
- foreach ($value as $conditionKey => $subCondition) {
222
- if (is_numeric($conditionKey)) {
223
- self::processConditions($subCondition, $groupedConditions, $bindings, $dbType, $prefix . $key . $conditionKey . '_', $level + 1);
224
- } else {
225
- self::processSingleCondition($conditionKey, $subCondition, $groupedConditions, $bindings, $dbType, $prefix . $key . $conditionKey . '_', $level + 1);
226
- }
227
- }
228
- if (!empty($groupedConditions)) {
229
- $conditionGroup = '(' . implode(" $key ", $groupedConditions) . ')';
230
- $sqlConditions[] = $conditionGroup;
231
- }
232
- }
233
- } else {
234
- self::processSingleCondition($key, $value, $sqlConditions, $bindings, $dbType, $prefix, $level);
235
- }
236
- }
237
- }
238
-
239
- private static function processSingleCondition($key, $value, &$sqlConditions, &$bindings, $dbType, $prefix, $level)
240
- {
241
- $fieldQuoted = ($dbType == 'pgsql' || $dbType == 'sqlite') ? "\"$key\"" : "`$key`";
242
- if (is_array($value)) {
243
- foreach ($value as $condition => $val) {
244
- $bindingKey = ":" . $prefix . $key . "_" . $condition . $level;
245
- switch ($condition) {
246
- case 'contains':
247
- case 'startsWith':
248
- case 'endsWith':
249
- case 'equals':
250
- case 'not':
251
- if ($val === null) {
252
- $sqlConditions[] = "$fieldQuoted IS NOT NULL";
253
- } elseif ($val === '') {
254
- $sqlConditions[] = "$fieldQuoted != ''";
255
- } else {
256
- $validatedValue = Validator::string($val);
257
- $likeOperator = $condition === 'contains' ? ($dbType == 'pgsql' ? 'ILIKE' : 'LIKE') : '=';
258
- if ($condition === 'startsWith') $validatedValue .= '%';
259
- if ($condition === 'endsWith') $validatedValue = '%' . $validatedValue;
260
- if ($condition === 'contains') $validatedValue = '%' . $validatedValue . '%';
261
- $sqlConditions[] = $condition === 'not' ? "$fieldQuoted != $bindingKey" : "$fieldQuoted $likeOperator $bindingKey";
262
- $bindings[$bindingKey] = $validatedValue;
263
- }
264
- break;
265
- case 'gt':
266
- case 'gte':
267
- case 'lt':
268
- case 'lte':
269
- if (is_float($val)) {
270
- $validatedValue = Validator::float($val);
271
- } elseif (is_int($val)) {
272
- $validatedValue = Validator::int($val);
273
- } elseif (strtotime($val) !== false) {
274
- $validatedValue = date('Y-m-d H:i:s', strtotime($val));
275
- } else {
276
- $validatedValue = Validator::string($val);
277
- }
278
- $operator = $condition === 'gt' ? '>' : ($condition === 'gte' ? '>=' : ($condition === 'lt' ? '<' : '<='));
279
- $sqlConditions[] = "$fieldQuoted $operator $bindingKey";
280
- $bindings[$bindingKey] = $validatedValue;
281
- break;
282
- case 'in':
283
- case 'notIn':
284
- $inPlaceholders = [];
285
- foreach ($val as $i => $inVal) {
286
- $inKey = $bindingKey . "_" . $i;
287
- $validatedValue = Validator::string($inVal);
288
- $inPlaceholders[] = $inKey;
289
- $bindings[$inKey] = $validatedValue;
290
- }
291
- $inClause = implode(', ', $inPlaceholders);
292
- $sqlConditions[] = "$fieldQuoted " . ($condition === 'notIn' ? 'NOT IN' : 'IN') . " ($inClause)";
293
- break;
294
- default:
295
- // Handle other conditions or log an error/warning for unsupported conditions
296
- throw new \Exception("Unsupported condition: $condition");
297
- break;
298
- }
299
- }
300
- } else {
301
- if ($value === null) {
302
- $sqlConditions[] = "$fieldQuoted IS NULL";
303
- } elseif ($value === '') {
304
- $sqlConditions[] = "$fieldQuoted = ''";
305
- } else {
306
- $bindingKey = ":" . $prefix . $key . $level;
307
- $validatedValue = Validator::string($value);
308
- $sqlConditions[] = "$fieldQuoted = $bindingKey";
309
- $bindings[$bindingKey] = $validatedValue;
310
- }
311
- }
312
- }
313
-
314
- private static function processNotCondition($conditions, &$sqlConditions, &$bindings, $dbType, $prefix, $level = 0)
315
- {
316
- foreach ($conditions as $key => $value) {
317
- self::processSingleCondition($key, $value, $sqlConditions, $bindings, $dbType, $prefix . 'NOT_', $level);
318
- }
319
- }
320
-
321
- public static function checkForInvalidKeys(array $data, array $fields, string $modelName)
322
- {
323
- foreach ($data as $key => $value) {
324
- if (!empty($key) && !in_array($key, $fields)) {
325
- throw new \Exception("The field '$key' does not exist in the $modelName model. Accepted fields: " . implode(', ', $fields));
326
- }
327
- }
328
- }
329
-
330
- public static function queryOptions(array $criteria, string &$sql)
331
- {
332
- // Handle _max, _min, _count, _avg, and _sum
333
- $selectParts = [];
334
- if (isset($criteria['_max'])) {
335
- foreach ($criteria['_max'] as $column => $enabled) {
336
- if ($enabled) {
337
- $selectParts[] = "MAX($column) AS max_$column";
338
- }
339
- }
340
- }
341
- if (isset($criteria['_min'])) {
342
- foreach ($criteria['_min'] as $column => $enabled) {
343
- if ($enabled) {
344
- $selectParts[] = "MIN($column) AS min_$column";
345
- }
346
- }
347
- }
348
- if (isset($criteria['_count'])) {
349
- foreach ($criteria['_count'] as $column => $enabled) {
350
- if ($enabled) {
351
- $selectParts[] = "COUNT($column) AS count_$column";
352
- }
353
- }
354
- }
355
- if (isset($criteria['_avg'])) {
356
- foreach ($criteria['_avg'] as $column => $enabled) {
357
- if ($enabled) {
358
- $selectParts[] = "AVG($column) AS avg_$column";
359
- }
360
- }
361
- }
362
- if (isset($criteria['_sum'])) {
363
- foreach ($criteria['_sum'] as $column => $enabled) {
364
- if ($enabled) {
365
- $selectParts[] = "SUM($column) AS sum_$column";
366
- }
367
- }
368
- }
369
-
370
- // Prepend to SELECT if _max, _min, _count, _avg, or _sum is specified
371
- if (!empty($selectParts)) {
372
- $sql = str_replace('SELECT', 'SELECT ' . implode(', ', $selectParts) . ',', $sql);
373
- }
374
-
375
- // Handle ORDER BY
376
- if (isset($criteria['orderBy'])) {
377
- $orderByParts = [];
378
-
379
- // Check if orderBy is an associative array with directions or a list of columns
380
- if (array_values($criteria['orderBy']) === $criteria['orderBy']) {
381
- // If it's a list of columns, default to 'asc'
382
- foreach ($criteria['orderBy'] as $column) {
383
- $orderByParts[] = "$column asc";
384
- }
385
- } else {
386
- // If it's an associative array with directions
387
- foreach ($criteria['orderBy'] as $column => $direction) {
388
- $direction = strtolower($direction) === 'desc' ? 'desc' : 'asc';
389
- $orderByParts[] = "$column $direction";
390
- }
391
- }
392
-
393
- $sql .= " ORDER BY " . implode(', ', $orderByParts);
394
- }
395
-
396
- // Handle LIMIT (take)
397
- if (isset($criteria['take'])) {
398
- $sql .= " LIMIT " . intval($criteria['take']);
399
- }
400
-
401
- // Handle OFFSET (skip)
402
- if (isset($criteria['skip'])) {
403
- $sql .= " OFFSET " . intval($criteria['skip']);
404
- }
405
- }
406
- }