prisma-client-php 0.0.8 → 0.0.9

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.
@@ -11,6 +11,7 @@ use Brick\Math\BigInteger;
11
11
  use ReflectionUnionType;
12
12
  use ReflectionNamedType;
13
13
  use Exception;
14
+ use PDO;
14
15
 
15
16
  enum ArrayType: string
16
17
  {
@@ -70,6 +71,8 @@ final class PPHPUtility
70
71
  if (is_string($key) && is_array($value)) {
71
72
  if (isset($value['select'])) {
72
73
  $relatedEntityFields[$key] = $value['select'];
74
+ } elseif (isset($value['include'])) {
75
+ $relatedEntityFields[$key] = $value['include'];
73
76
  } else {
74
77
  if (is_array($value) && empty($value)) {
75
78
  $relatedEntityFields[$key] = [$key];
@@ -97,17 +100,12 @@ final class PPHPUtility
97
100
  continue;
98
101
  }
99
102
 
100
- $isRelationalOrInverse = false;
101
- if (isset($fields[$fieldName]['decorators'])) {
102
- foreach ($fields[$fieldName]['decorators'] as $decoratorKey => $decoratorValue) {
103
- if ($decoratorKey === 'relation' || $decoratorKey === 'inverseRelation') {
104
- $isRelationalOrInverse = true;
105
- break;
106
- }
107
- }
103
+ $isObject = false;
104
+ if (isset($fields[$fieldName]) && $fields[$fieldName]['kind'] === 'object') {
105
+ $isObject = true;
108
106
  }
109
107
 
110
- if (!$isRelationalOrInverse) {
108
+ if (!$isObject) {
111
109
  if (in_array($fieldName, $primaryEntityFields)) continue;
112
110
  $primaryEntityFields[] = $fieldName;
113
111
  }
@@ -210,7 +208,7 @@ final class PPHPUtility
210
208
  return ArrayType::Value;
211
209
  }
212
210
  }
213
- return ArrayType::Value; // Default return value if array is empty
211
+ return ArrayType::Value;
214
212
  }
215
213
 
216
214
  /**
@@ -546,38 +544,6 @@ final class PPHPUtility
546
544
  return ($dbType === 'pgsql' || $dbType === 'sqlite') ? "\"$column\"" : "`$column`";
547
545
  }
548
546
 
549
- /**
550
- * Parses a column string into an array of segments.
551
- *
552
- * This method performs the following steps:
553
- * 1. Replaces occurrences of '._.' with '._ARRAY_.' in the input string.
554
- * 2. Splits the modified string on '.' to create an array of parts.
555
- * 3. Converts '_ARRAY_' placeholders into special markers in the resulting array.
556
- *
557
- * @param string $column The column string to be parsed.
558
- * @return array An array of segments derived from the input column string.
559
- */
560
- public static function parseColumn(string $column): array
561
- {
562
- // Step 1: replace ._. with ._ARRAY_.
563
- $column = str_replace('._.', '._ARRAY_.', $column);
564
-
565
- // Step 2: split on '.'
566
- $parts = explode('.', $column);
567
-
568
- // Step 3: convert '_ARRAY_' placeholders into special markers in the array
569
- $segments = [];
570
- foreach ($parts as $part) {
571
- if ($part === '_ARRAY_') {
572
- $segments[] = '_ARRAY_';
573
- } else {
574
- $segments[] = $part;
575
- }
576
- }
577
-
578
- return $segments;
579
- }
580
-
581
547
  /**
582
548
  * Recursively builds SQL JOIN statements and SELECT fields for nested relations.
583
549
  *
@@ -596,7 +562,7 @@ final class PPHPUtility
596
562
  string $parentAlias,
597
563
  array &$joins,
598
564
  array &$selectFields,
599
- mixed $pdo,
565
+ PDO $pdo,
600
566
  string $dbType,
601
567
  ?object $model = null,
602
568
  string $defaultJoinType = 'INNER JOIN',
@@ -619,19 +585,19 @@ final class PPHPUtility
619
585
  $isNested = !empty($nestedInclude);
620
586
 
621
587
  // 1. Fetch metadata
622
- if (!isset($model->fields[$relationName])) {
588
+ if (!isset($model->_fields[$relationName])) {
623
589
  throw new Exception("Relation metadata not defined for '$relationName' in " . get_class($model));
624
590
  }
625
591
 
626
592
  // 2. Identify related class
627
- $relatedClassName = "Lib\\Prisma\\Classes\\" . $model->fields[$relationName]['type'] ?? null;
593
+ $relatedClassName = "Lib\\Prisma\\Classes\\" . $model->_fields[$relationName]['type'] ?? null;
628
594
  $relatedClass = new $relatedClassName($pdo);
629
595
  if (!$relatedClass) {
630
596
  throw new Exception("Could not instantiate class for relation '$relationName'.");
631
597
  }
632
598
 
633
599
  // 3. Determine DB table
634
- $joinTable = $relatedClass->tableName ?? null;
600
+ $joinTable = $relatedClass->_tableName ?? null;
635
601
  if (!$joinTable) {
636
602
  throw new Exception("No valid table name found for relation '$relationName'.");
637
603
  }
@@ -640,7 +606,7 @@ final class PPHPUtility
640
606
 
641
607
  // 5. Build the ON condition
642
608
  $joinConditions = [];
643
- $fieldsRelatedWithKeys = $model->fieldsRelatedWithKeys[$relationName] ?? null;
609
+ $fieldsRelatedWithKeys = $model->_fieldsRelatedWithKeys[$relationName] ?? null;
644
610
  if ($fieldsRelatedWithKeys) {
645
611
  $relationToFields = $fieldsRelatedWithKeys['relationToFields'] ?? [];
646
612
  $relationFromFields = $fieldsRelatedWithKeys['relationFromFields'] ?? [];
@@ -655,7 +621,7 @@ final class PPHPUtility
655
621
  throw new Exception("Missing references/fields for '$relationName' at index $index.");
656
622
  }
657
623
 
658
- $fromFieldExists = array_key_exists($fromField, $model->fields);
624
+ $fromFieldExists = array_key_exists($fromField, $model->_fields);
659
625
 
660
626
  if ($fromFieldExists) {
661
627
  $joinConditions[] = sprintf(
@@ -729,129 +695,292 @@ final class PPHPUtility
729
695
  }
730
696
  }
731
697
 
732
- public static function arrayToObjectRecursive($data)
698
+ public static function compareStringsAlphabetically($string1, $string2)
733
699
  {
734
- // If it's not an array, there's nothing to convert; just return as-is
735
- if (!is_array($data)) {
736
- return $data;
700
+ $lowerString1 = strtolower($string1);
701
+ $lowerString2 = strtolower($string2);
702
+
703
+ if ($lowerString1 < $lowerString2) {
704
+ return [
705
+ 'A' => $string1,
706
+ 'B' => $string2,
707
+ 'Name' => "_" . ucfirst($string1) . "To" . ucfirst($string2)
708
+ ];
709
+ } else {
710
+ return [
711
+ 'A' => $string2,
712
+ 'B' => $string1,
713
+ 'Name' => "_" . ucfirst($string2) . "To" . ucfirst($string1)
714
+ ];
737
715
  }
716
+ }
738
717
 
739
- // Convert each item in the array and then convert the array itself
740
- foreach ($data as $key => $value) {
741
- $data[$key] = self::arrayToObjectRecursive($value);
718
+ public static function processRelation(
719
+ string $fieldName,
720
+ array $fieldData,
721
+ array $relationFromFields,
722
+ array $relationToFields,
723
+ string $relatedClassName,
724
+ PDO $pdo,
725
+ bool $requestOption = true,
726
+ ): array {
727
+ if (count($relationFromFields) !== count($relationToFields)) {
728
+ throw new Exception("Mismatch between 'relationFromFields' and 'relationToFields' for relation '$fieldName'.");
729
+ }
730
+
731
+ $reflection = new ReflectionClass($relatedClassName);
732
+ $relatedClass = $reflection->newInstance($pdo);
733
+
734
+ if (isset($fieldData['create'])) {
735
+ $relatedData = ['data' => $fieldData['create']];
736
+ $relatedResult = $relatedClass->create($relatedData);
737
+ } elseif (isset($fieldData['connect'])) {
738
+ $relatedData = ['where' => $fieldData['connect']];
739
+ $relatedResult = $relatedClass->findUnique($relatedData);
740
+ } elseif (isset($fieldData['connectOrCreate'])) {
741
+ $relatedData = ['where' => $fieldData['connectOrCreate']['where']];
742
+ $relatedResult = $relatedClass->findUnique($relatedData);
743
+
744
+ if (!$relatedResult) {
745
+ $relatedData = ['data' => $fieldData['connectOrCreate']['create']];
746
+ $relatedResult = $relatedClass->create($relatedData);
747
+ }
748
+ } else {
749
+ throw new Exception("No valid action provided for relation '$fieldName'.");
750
+ }
751
+
752
+ $relatedResult = (array) $relatedResult;
753
+
754
+ if (!$requestOption) {
755
+ return $relatedResult;
756
+ }
757
+
758
+ if (!$relatedResult) {
759
+ throw new Exception("Failed to process related record for '$fieldName'.");
742
760
  }
743
761
 
744
- return (object) $data;
762
+ $bindings = [];
763
+ foreach ($relationFromFields as $index => $fromField) {
764
+ $toField = $relationToFields[$index];
765
+ if (!isset($relatedResult[$toField])) {
766
+ throw new Exception("The field '$toField' is missing in the related data for '$fieldName'.");
767
+ }
768
+
769
+ $bindings[$fromField] = $relatedResult[$toField];
770
+ }
771
+
772
+ return $bindings;
745
773
  }
746
774
 
747
- public static function arrayToClassRecursive(array $data, string $class)
748
- {
749
- if (!class_exists($class)) {
750
- throw new InvalidArgumentException("Class {$class} does not exist.");
751
- }
752
-
753
- $reflection = new ReflectionClass($class);
754
- $instance = $reflection->newInstanceWithoutConstructor();
755
- $properties = $reflection->getProperties();
756
-
757
- foreach ($properties as $property) {
758
- $propertyName = $property->getName();
759
-
760
- if (array_key_exists($propertyName, $data)) {
761
- $propertyType = $property->getType();
762
- $typeNames = [];
763
-
764
- if ($propertyType instanceof ReflectionUnionType) {
765
- $typeNames = array_map(fn($t) => $t->getName(), $propertyType->getTypes());
766
- } elseif ($propertyType instanceof ReflectionNamedType) {
767
- $typeNames[] = $propertyType->getName();
768
- }
769
-
770
- if (in_array(DateTime::class, $typeNames)) {
771
- $instance->$propertyName = !empty($data[$propertyName])
772
- ? new DateTime($data[$propertyName])
773
- : null;
774
- } elseif (in_array(BigDecimal::class, $typeNames)) {
775
- $instance->$propertyName = BigDecimal::of($data[$propertyName]);
776
- } elseif (in_array(BigInteger::class, $typeNames)) {
777
- $instance->$propertyName = BigInteger::of($data[$propertyName]);
778
- } elseif (count(array_intersect($typeNames, ['int', 'float', 'string', 'bool'])) > 0) {
779
- $instance->$propertyName = $data[$propertyName];
780
- } elseif (in_array('array', $typeNames) && isset($data[$propertyName]) && is_array($data[$propertyName])) {
781
- // Check array type
782
- $arrayType = self::checkArrayContents($data[$propertyName]);
783
-
784
- // Handle array-to-object conversion
785
- $docComment = $property->getDocComment();
786
- if ($docComment && preg_match('/@var\s+([^\s\[\]]+)\[]/', $docComment, $matches)) {
787
- $elementType = $matches[1];
788
- if (class_exists($elementType)) {
789
- if ($arrayType === ArrayType::Indexed) {
790
- $instance->$propertyName = array_map(
791
- fn($item) => self::arrayToClassRecursive($item, $elementType),
792
- $data[$propertyName]
793
- );
775
+ public static function mergeForeignKeysIfNeeded(
776
+ array $item,
777
+ string $action,
778
+ array $relationToFields,
779
+ array $relationFromFields,
780
+ $lastInsertId,
781
+ array $fields
782
+ ): array {
783
+ foreach ($relationToFields as $idx => $toField) {
784
+ if (!array_key_exists($toField, $fields)) {
785
+ continue;
786
+ }
787
+
788
+ $fromField = $relationFromFields[$idx] ?? null;
789
+ if (!$fromField) {
790
+ continue;
791
+ }
792
+
793
+ if ($action === 'create') {
794
+ $item[$fromField] = $lastInsertId;
795
+ } elseif ($action === 'connect') {
796
+ $item[$fromField] = $lastInsertId;
797
+ } elseif ($action === 'connectOrCreate') {
798
+ if (isset($item['where'])) {
799
+ $item['where'][$fromField] = $lastInsertId;
800
+ }
801
+ }
802
+ }
803
+
804
+ return $item;
805
+ }
806
+
807
+ public static function populateIncludedRelations(
808
+ array $records,
809
+ array $includes,
810
+ array $fields,
811
+ array $fieldsRelatedWithKeys,
812
+ PDO $pdo
813
+ ): array {
814
+ $isSingle = !isset($records[0]) || !is_array($records[0]);
815
+ if ($isSingle) {
816
+ $records = [$records];
817
+ }
818
+
819
+ foreach ($records as $recordIndex => $singleRecord) {
820
+ foreach ($includes as $key => $value) {
821
+ if (!isset($fields[$key]) || !isset($fieldsRelatedWithKeys[$key])) {
822
+ continue;
823
+ }
824
+
825
+ $relatedField = $fields[$key];
826
+ $relatedFieldKeys = $fieldsRelatedWithKeys[$key];
827
+
828
+ $relatedClass = "Lib\\Prisma\\Classes\\" . $relatedField['type'];
829
+ if (!class_exists($relatedClass)) {
830
+ throw new Exception("Class $relatedClass does not exist.");
831
+ }
832
+
833
+ $reflectionClass = new ReflectionClass($relatedClass);
834
+ $relatedInstance = $reflectionClass->newInstance($pdo);
835
+ $relatedInstanceField = $relatedInstance->_fieldByRelationName[$relatedField['relationName']];
836
+
837
+ $whereConditions = [];
838
+ foreach ($relatedFieldKeys['relationFromFields'] as $index => $fromField) {
839
+ $toField = $relatedFieldKeys['relationToFields'][$index];
840
+
841
+ if (!array_key_exists($toField, $singleRecord)) {
842
+ continue 2;
843
+ }
844
+
845
+ if (empty($relatedInstanceField['relationFromFields']) && empty($relatedInstanceField['relationToFields'])) {
846
+ $whereConditions[$toField] = $singleRecord[$toField];
847
+ } elseif ($relatedInstanceField['isList']) {
848
+ $whereConditions[$toField] = $singleRecord[$fromField];
849
+ } else {
850
+ $whereConditions[$fromField] = $singleRecord[$toField];
851
+ }
852
+ }
853
+
854
+ if (isset($value['where']) && is_array($value['where'])) {
855
+ $whereConditions = array_merge($whereConditions, $value['where']);
856
+ }
857
+
858
+ $selectFields = [];
859
+ if (isset($value['select']) && is_array($value['select'])) {
860
+ foreach ($value['select'] as $field => $subSelect) {
861
+ if (is_array($subSelect)) {
862
+ $selectFields[$field] = $subSelect;
863
+ } elseif ((bool) $subSelect === true) {
864
+ if (is_numeric($field)) {
865
+ $selectFields[$field] = $subSelect;
866
+ } else {
867
+ $selectFields[$field] = true;
868
+ }
869
+ }
870
+ }
871
+ }
872
+
873
+ $includeFields = [];
874
+ if (isset($value['include']) && is_array($value['include'])) {
875
+ foreach ($value['include'] as $field => $subInclude) {
876
+ if (is_array($subInclude)) {
877
+ $includeFields[$field] = $subInclude;
878
+ } elseif ((bool) $subInclude === true) {
879
+ if (is_numeric($field)) {
880
+ $includeFields[$field] = $subInclude;
794
881
  } else {
795
- // If associative, keep as array
796
- $instance->$propertyName = $data[$propertyName];
882
+ $includeFields[$field] = true;
797
883
  }
884
+ }
885
+ }
886
+ }
887
+
888
+ $omitFields = [];
889
+ if (isset($value['omit']) && is_array($value['omit'])) {
890
+ foreach ($value['omit'] as $field => $shouldOmit) {
891
+ if ((bool) $shouldOmit === true) {
892
+ $omitFields[$field] = true;
893
+ }
894
+ }
895
+ }
896
+
897
+ $queryOptions = ['where' => $whereConditions];
898
+ if (!empty($selectFields)) {
899
+ $queryOptions['select'] = $selectFields;
900
+ }
901
+
902
+ if (!empty($includeFields)) {
903
+ $queryOptions['include'] = $includeFields;
904
+ }
905
+
906
+ if (!empty($omitFields)) {
907
+ $queryOptions['omit'] = $omitFields;
908
+ }
909
+
910
+ if ($relatedField['isList'] && $relatedInstanceField['isList']) {
911
+ if ($relatedField['type'] === $relatedInstanceField['type']) {
912
+ if (isset($queryOptions['where']) && empty($queryOptions['where'])) {
913
+ unset($queryOptions['where']);
914
+ }
915
+
916
+ if (empty($queryOptions)) {
917
+ $relatedFieldResult = $relatedInstance->findMany();
798
918
  } else {
799
- $instance->$propertyName = $data[$propertyName]; // Default to raw array
919
+ $relatedFieldResult = $relatedInstance->findMany($queryOptions);
800
920
  }
801
921
  } else {
802
- $instance->$propertyName = $data[$propertyName]; // Default to raw array
803
- }
804
- } else {
805
- foreach ($typeNames as $typeName) {
806
- if (class_exists($typeName)) {
807
- if (is_array($data[$propertyName])) {
808
- $arrayType = self::checkArrayContents($data[$propertyName]);
809
-
810
- if ($arrayType === ArrayType::Associative) {
811
- $instance->$propertyName = self::arrayToClassRecursive($data[$propertyName], $typeName);
812
- } elseif ($arrayType === ArrayType::Indexed) {
813
- $instance->$propertyName = array_map(
814
- fn($item) => self::arrayToClassRecursive($item, $typeName),
815
- $data[$propertyName] ?? []
816
- );
922
+ $implicitModelInfo = PPHPUtility::compareStringsAlphabetically($relatedField['type'], $relatedInstanceField['type']);
923
+ $searchColumn = ($relatedField['type'] === $implicitModelInfo['A']) ? 'B' : 'A';
924
+ $returnColumn = ($searchColumn === 'A') ? 'B' : 'A';
925
+
926
+ $idField = null;
927
+ foreach ($relatedField['relationFromFields'] as $index => $fromField) {
928
+ if (isset($singleRecord[$fromField])) {
929
+ $idField = $fromField;
930
+ break;
931
+ }
932
+ }
933
+
934
+ if (!$idField) {
935
+ foreach ($relatedInstance->_fields as $field) {
936
+ if ($field['isId']) {
937
+ $idField = $field['name'];
938
+ break;
817
939
  }
818
- } elseif ($data[$propertyName] instanceof $typeName) {
819
- $instance->$propertyName = $data[$propertyName];
820
940
  }
821
- break;
941
+ }
942
+
943
+ if (!$idField || !isset($singleRecord[$idField])) {
944
+ throw new Exception("No valid ID field found for relation lookup.");
945
+ }
946
+
947
+ $idValue = $singleRecord[$idField];
948
+
949
+ $sql = "SELECT " . $returnColumn . " FROM " . $implicitModelInfo['Name'] . " WHERE " . $searchColumn . " = :id";
950
+ $stmt = $pdo->prepare($sql);
951
+ $stmt->execute(['id' => $idValue]);
952
+ $implicitRecords = $stmt->fetchAll(PDO::FETCH_COLUMN);
953
+
954
+ if (!empty($implicitRecords)) {
955
+ $queryOptions['where'] = array_merge($queryOptions['where'] ?? [], [
956
+ 'id' => [
957
+ 'in' => $implicitRecords
958
+ ]
959
+ ]);
960
+ $queryOptions = array_filter($queryOptions, fn($value) => !empty($value));
961
+ $relatedFieldResult = $relatedInstance->findMany($queryOptions);
962
+ } else {
963
+ $relatedFieldResult = [];
822
964
  }
823
965
  }
966
+ } else {
967
+ if ($relatedField['isList']) {
968
+ $relatedFieldResult = $relatedInstance->findMany($queryOptions);
969
+ } else {
970
+ $relatedFieldResult = $relatedInstance->findUnique($queryOptions);
971
+ }
824
972
  }
825
- }
826
- }
827
-
828
- return $instance;
829
- }
830
973
 
831
- /**
832
- * Recursively sets a value in a multi-dimensional array
833
- * based on an array of keys. E.g.:
834
- * setNestedValue($arr, ['post','categories','id'], 'some-id')
835
- * becomes
836
- * $arr['post']['categories']['id'] = 'some-id';
837
- */
838
- public static function setNestedValue(array &$array, array $keys, $value)
839
- {
840
- // Take the first key from the array
841
- $key = array_shift($keys);
974
+ $singleRecord[$key] = $relatedFieldResult;
975
+ }
842
976
 
843
- // If this key doesn't exist yet, initialize it
844
- if (!isset($array[$key])) {
845
- // Decide if you want an empty array or some default
846
- $array[$key] = [];
977
+ $records[$recordIndex] = $singleRecord;
847
978
  }
848
979
 
849
- // If there are no more keys left, this is where we set the final value
850
- if (empty($keys)) {
851
- $array[$key] = $value;
852
- } else {
853
- // Otherwise, we recurse deeper
854
- self::setNestedValue($array[$key], $keys, $value);
980
+ if ($isSingle) {
981
+ return $records[0];
855
982
  }
983
+
984
+ return $records;
856
985
  }
857
986
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "prisma-client-php",
3
3
  "description": "Prisma Client PHP is an auto-generated query builder that enables type-safe database access in PHP.",
4
- "version": "0.0.8",
4
+ "version": "0.0.9",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
7
  "scripts": {