@@ -14,6 +14,8 @@ final class ArrayParser
1414 use ForbidCloning;
1515 use ForbidSerialization;
1616
17+ private const WHITESPACE_CHARS = " \n\r\t\v\0" ;
18+
1719 /**
1820 * @param string $data String representation of PostgresSQL array.
1921 * @param \Closure(string):mixed $cast Callback to cast parsed values.
@@ -25,12 +27,10 @@ final class ArrayParser
2527 */
2628 public static function parse (string $ data , \Closure $ cast , string $ delimiter = ', ' ): array
2729 {
28- $ data = \trim ($ data );
29-
3030 $ parser = new self ($ data , $ cast , $ delimiter );
3131 $ result = $ parser ->parseToArray ();
3232
33- if ($ parser ->data !== '' ) {
33+ if (isset ( $ parser ->data [ $ parser -> position ]) ) {
3434 throw new PostgresParseException ("Data left in buffer after parsing " );
3535 }
3636
@@ -43,9 +43,10 @@ public static function parse(string $data, \Closure $cast, string $delimiter = '
4343 * @param string $delimiter Delimiter used to separate values.
4444 */
4545 private function __construct (
46- private string $ data ,
46+ private readonly string $ data ,
4747 private readonly \Closure $ cast ,
48- private readonly string $ delimiter = ', ' ,
48+ private readonly string $ delimiter ,
49+ private int $ position = 0 ,
4950 ) {
5051 }
5152
@@ -58,36 +59,39 @@ private function parseToArray(): array
5859 {
5960 $ result = [];
6061
61- if ($ this ->data === '' ) {
62+ $ this ->position = $ this ->skipWhitespace ($ this ->position );
63+
64+ if (!isset ($ this ->data [$ this ->position ])) {
6265 throw new PostgresParseException ("Unexpected end of data " );
6366 }
6467
65- if ($ this ->data [0 ] !== '{ ' ) {
68+ if ($ this ->data [$ this -> position ] !== '{ ' ) {
6669 throw new PostgresParseException ("Missing opening bracket " );
6770 }
6871
69- $ this ->data = \ltrim ( \substr ( $ this ->data , 1 ) );
72+ $ this ->position = $ this -> skipWhitespace ( $ this ->position + 1 );
7073
7174 do {
72- if ($ this ->data === '' ) {
75+ if (! isset ( $ this ->data [ $ this -> position ]) ) {
7376 throw new PostgresParseException ("Unexpected end of data " );
7477 }
7578
76- if ($ this ->data [0 ] === '} ' ) { // Empty array
77- $ this ->data = \ltrim ( \substr ( $ this ->data , 1 ) );
79+ if ($ this ->data [$ this -> position ] === '} ' ) { // Empty array
80+ $ this ->position = $ this -> skipWhitespace ( $ this ->position + 1 );
7881 break ;
7982 }
8083
81- if ($ this ->data [0 ] === '{ ' ) { // Array
82- $ parser = new self ($ this ->data , $ this ->cast , $ this ->delimiter );
84+ if ($ this ->data [$ this -> position ] === '{ ' ) { // Array
85+ $ parser = new self ($ this ->data , $ this ->cast , $ this ->delimiter , $ this -> position );
8386 $ result [] = $ parser ->parseToArray ();
84- $ this ->data = $ parser ->data ;
85- $ end = $ this ->trim ( 0 );
87+ $ this ->position = $ parser ->position ;
88+ $ delimiter = $ this ->moveToNextDelimiter ( $ this -> position );
8689 continue ;
8790 }
8891
89- if ($ this ->data [0 ] === '" ' ) { // Quoted value
90- for ($ position = 1 ; isset ($ this ->data [$ position ]); ++$ position ) {
92+ if ($ this ->data [$ this ->position ] === '" ' ) { // Quoted value
93+ ++$ this ->position ;
94+ for ($ position = $ this ->position ; isset ($ this ->data [$ position ]); ++$ position ) {
9195 if ($ this ->data [$ position ] === '\\' ) {
9296 ++$ position ; // Skip next character
9397 continue ;
@@ -102,27 +106,30 @@ private function parseToArray(): array
102106 throw new PostgresParseException ("Could not find matching quote in quoted value " );
103107 }
104108
105- $ yield = \stripslashes (\substr ($ this ->data , 1 , $ position - 1 ));
109+ $ entry = \stripslashes (\substr ($ this ->data , $ this -> position , $ position - $ this -> position ));
106110
107- $ end = $ this ->trim ($ position + 1 );
111+ $ delimiter = $ this ->moveToNextDelimiter ($ position + 1 );
108112 } else { // Unquoted value
109- $ position = 0 ;
110- while (isset ($ this ->data [$ position ]) && $ this ->data [$ position ] !== $ this ->delimiter && $ this ->data [$ position ] !== '} ' ) {
113+ $ position = $ this ->position ;
114+ while (isset ($ this ->data [$ position ])
115+ && $ this ->data [$ position ] !== $ this ->delimiter
116+ && $ this ->data [$ position ] !== '} '
117+ ) {
111118 ++$ position ;
112119 }
113120
114- $ yield = \trim (\substr ($ this ->data , 0 , $ position ));
121+ $ entry = \trim (\substr ($ this ->data , $ this -> position , $ position - $ this -> position ));
115122
116- $ end = $ this ->trim ($ position );
123+ $ delimiter = $ this ->moveToNextDelimiter ($ position );
117124
118- if (\strcasecmp ($ yield , "NULL " ) === 0 ) { // Literal NULL is always unquoted.
125+ if (\strcasecmp ($ entry , "NULL " ) === 0 ) { // Literal NULL is always unquoted.
119126 $ result [] = null ;
120127 continue ;
121128 }
122129 }
123130
124- $ result [] = ($ this ->cast )($ yield );
125- } while ($ end !== '} ' );
131+ $ result [] = ($ this ->cast )($ entry );
132+ } while ($ delimiter !== '} ' );
126133
127134 return $ result ;
128135 }
@@ -134,22 +141,31 @@ private function parseToArray(): array
134141 *
135142 * @throws PostgresParseException
136143 */
137- private function trim (int $ position ): string
144+ private function moveToNextDelimiter (int $ position ): string
138145 {
139- $ this -> data = \ltrim ( \substr ( $ this ->data , $ position) );
146+ $ position = $ this ->skipWhitespace ( $ position );
140147
141- if ($ this ->data === '' ) {
148+ if (! isset ( $ this ->data [ $ position ]) ) {
142149 throw new PostgresParseException ("Unexpected end of data " );
143150 }
144151
145- $ end = $ this ->data [0 ];
152+ $ delimiter = $ this ->data [$ position ];
146153
147- if ($ end !== $ this ->delimiter && $ end !== '} ' ) {
154+ if ($ delimiter !== $ this ->delimiter && $ delimiter !== '} ' ) {
148155 throw new PostgresParseException ("Invalid delimiter " );
149156 }
150157
151- $ this ->data = \ltrim (\substr ($ this ->data , 1 ));
158+ $ this ->position = $ this ->skipWhitespace ($ position + 1 );
159+
160+ return $ delimiter ;
161+ }
162+
163+ private function skipWhitespace (int $ position ): int
164+ {
165+ while (isset ($ this ->data [$ position ]) && \str_contains (self ::WHITESPACE_CHARS , $ this ->data [$ position ])) {
166+ ++$ position ;
167+ }
152168
153- return $ end ;
169+ return $ position ;
154170 }
155171}
0 commit comments