@@ -11,11 +11,13 @@ abstract class AbstractEntityRepository implements EntityRepositoryInterface
1111 use EntityManagerTrait;
1212
1313 private string $ entityType ;
14+ private string $ entityTypeXPathLiteral ;
1415 private string $ entityClass ;
1516
1617 public function __construct (string $ entityType )
1718 {
1819 $ this ->entityType = $ entityType ;
20+ $ this ->entityTypeXPathLiteral = $ this ->toXPathValue ($ entityType );
1921 $ this ->entityClass = $ this ->getEntityByEntityType ($ entityType );
2022 $ this ->init ();
2123 }
@@ -30,7 +32,11 @@ public function getEntityType(): string
3032 */
3133 public function findAll (): ?Collection
3234 {
33- $ nodes = $ this ->xpath ->query (\sprintf ('//item[@type="%s"] ' , $ this ->entityType ));
35+ $ nodes = $ this ->queryNodes (\sprintf ('//item[@type=%s] ' , $ this ->entityTypeXPathLiteral ));
36+ if ($ nodes === null ) {
37+ return null ;
38+ }
39+
3440 if ($ nodes ->length < 1 ) {
3541 return null ;
3642 }
@@ -42,16 +48,24 @@ public function findAll(): ?Collection
4248
4349 public function find (string $ id ): ?EntityInterface
4450 {
45- $ node = $ this ->xpath ->query (\sprintf ('//item[@type="%s" and @id="%s"] ' , $ this ->entityType , $ id ));
46- if ($ node ->length > 1 ) {
51+ $ nodes = $ this ->queryNodes (\sprintf (
52+ '//item[@type=%s and @id=%s] ' ,
53+ $ this ->entityTypeXPathLiteral ,
54+ $ this ->toXPathValue ($ id )
55+ ));
56+ if ($ nodes === null ) {
57+ return null ;
58+ }
59+
60+ if ($ nodes ->length > 1 ) {
4761 throw new \Exception ('Multiple entities found with the same ID. ' );
4862 }
4963
50- if ($ node ->length < 1 ) {
64+ if ($ nodes ->length < 1 ) {
5165 return null ;
5266 }
5367
54- $ array = $ this ->serializer ->decode ($ node , SchemaEncoder::FORMAT );
68+ $ array = $ this ->serializer ->decode ($ nodes , SchemaEncoder::FORMAT );
5569
5670 return $ this ->serializer ->denormalize ($ array , $ this ->entityClass );
5771 }
@@ -72,18 +86,31 @@ public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = nu
7286 */
7387 public function findOneBy (array $ criteria , ?array $ orderBy = null ): ?EntityInterface
7488 {
75- $ additionalArgs = '' ;
89+ if (isset ($ criteria ['id ' ]) && \count ($ criteria ) === 1 ) {
90+ return $ this ->find ((string )$ criteria ['id ' ]);
91+ }
92+
93+ $ predicates = [\sprintf ('@type=%s ' , $ this ->entityTypeXPathLiteral )];
7694 if (isset ($ criteria ['id ' ])) {
77- $ additionalArgs . = \sprintf (' and @id="%s" ' , $ criteria ['id ' ]);
95+ $ predicates [] = \sprintf ('@id=%s ' , $ this -> toXPathValue (( string ) $ criteria ['id ' ]) );
7896 unset($ criteria ['id ' ]);
7997 }
8098
8199 foreach ($ criteria as $ key => $ value ) {
82- $ additionalArgs .= \sprintf (' and ./fragment[@name="%s"] = "%s" ' , \trim ($ key ), \trim ((string )$ value ));
100+ $ predicates [] = \sprintf (
101+ './fragment[@name=%s]=%s ' ,
102+ $ this ->toXPathValue ($ key ),
103+ $ this ->toXPathValue ((string )$ value )
104+ );
105+ }
106+
107+ $ query = '//item[ ' . \implode (' and ' , $ predicates ) . '] ' ;
108+ $ nodes = $ this ->queryNodes ($ query );
109+ if ($ nodes === null ) {
110+ return null ;
83111 }
84112
85- $ query = \sprintf ('//item[@type="%s" %s] ' , $ this ->entityType , $ additionalArgs );
86- $ node = $ this ->xpath ->query ($ query )->item (0 );
113+ $ node = $ nodes ->item (0 );
87114 if ($ node === null ) {
88115 return null ;
89116 }
@@ -97,4 +124,39 @@ public function remove(string $id): void
97124 {
98125 $ this ->removeById ($ id );
99126 }
127+
128+ /**
129+ * @return \DOMNodeList<\DOMNode>|null
130+ */
131+ private function queryNodes (string $ query ): ?\DOMNodeList
132+ {
133+ $ nodes = $ this ->xpath ->query ($ query );
134+
135+ return ($ nodes === false ) ? null : $ nodes ;
136+ }
137+
138+ private function xpathLiteral (string $ value ): string
139+ {
140+ if (!\str_contains ($ value , "' " )) {
141+ return "' {$ value }' " ;
142+ }
143+
144+ if (!\str_contains ($ value , '" ' )) {
145+ return '" ' . $ value . '" ' ;
146+ }
147+
148+ $ parts = \explode ("' " , $ value );
149+ $ quotedParts = \array_map (static fn (string $ part ): string => "' {$ part }' " , $ parts );
150+
151+ return 'concat( ' . \implode (', " \'", ' , $ quotedParts ) . ') ' ;
152+ }
153+
154+ private function toXPathValue (string $ value ): string
155+ {
156+ if (!\str_contains ($ value , "' " ) && !\str_contains ($ value , '" ' )) {
157+ return '" ' . $ value . '" ' ;
158+ }
159+
160+ return $ this ->xpathLiteral ($ value );
161+ }
100162}
0 commit comments