Skip to content

Commit 6e3a63a

Browse files
authored
feat(string-helper): add replacement and concatenation methods (#517)
1 parent f0f272b commit 6e3a63a

File tree

2 files changed

+205
-26
lines changed

2 files changed

+205
-26
lines changed

src/Tempest/Support/src/StringHelper.php

Lines changed: 137 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -128,62 +128,106 @@ public function finish(string $cap): self
128128
return new self(preg_replace('/(?:' . preg_quote($cap, '/') . ')+$/u', replacement: '', subject: $this->string) . $cap);
129129
}
130130

131-
public function after(string|int $search): self
131+
public function after(string|array $search): self
132132
{
133-
if ($search === '') {
133+
if ($search === '' || $search === []) {
134+
return $this;
135+
}
136+
137+
$nearestPosition = mb_strlen($this->string); // Initialize with a large value
138+
$foundSearch = '';
139+
140+
foreach (arr($search) as $term) {
141+
$position = mb_strpos($this->string, $term);
142+
143+
if ($position !== false && $position < $nearestPosition) {
144+
$nearestPosition = $position;
145+
$foundSearch = $term;
146+
}
147+
}
148+
149+
if ($nearestPosition === mb_strlen($this->string)) {
134150
return $this;
135151
}
136152

137-
$string = array_reverse(explode((string) $search, $this->string, limit: 2))[0];
153+
$string = mb_substr($this->string, $nearestPosition + mb_strlen($foundSearch));
138154

139155
return new self($string);
140156
}
141157

142-
public function afterLast(string|int $search): self
158+
public function afterLast(string|array $search): self
143159
{
144-
if ($search === '') {
160+
if ($search === '' || $search === []) {
145161
return $this;
146162
}
147163

148-
$position = strrpos($this->string, (string) $search);
164+
$farthestPosition = -1;
165+
$foundSearch = null;
149166

150-
if ($position === false) {
167+
foreach (arr($search) as $term) {
168+
$position = mb_strrpos($this->string, $term);
169+
170+
if ($position !== false && $position > $farthestPosition) {
171+
$farthestPosition = $position;
172+
$foundSearch = $term;
173+
}
174+
}
175+
176+
if ($farthestPosition === -1 || $foundSearch === null) {
151177
return $this;
152178
}
153179

154-
$string = substr($this->string, $position + strlen((string) $search));
180+
$string = mb_substr($this->string, $farthestPosition + strlen($foundSearch));
155181

156182
return new self($string);
157183
}
158184

159-
public function before(string|int $search): self
185+
public function before(string|array $search): self
160186
{
161-
if ($search === '') {
187+
if ($search === '' || $search === []) {
162188
return $this;
163189
}
164190

165-
$string = strstr($this->string, (string) $search, before_needle: true);
191+
$nearestPosition = mb_strlen($this->string);
192+
193+
foreach (arr($search) as $char) {
194+
$position = mb_strpos($this->string, $char);
166195

167-
if ($string === false) {
196+
if ($position !== false && $position < $nearestPosition) {
197+
$nearestPosition = $position;
198+
}
199+
}
200+
201+
if ($nearestPosition === mb_strlen($this->string)) {
168202
return $this;
169203
}
170204

205+
$string = mb_substr($this->string, start: 0, length: $nearestPosition);
206+
171207
return new self($string);
172208
}
173209

174-
public function beforeLast(string|int $search): self
210+
public function beforeLast(string|array $search): self
175211
{
176-
if ($search === '') {
212+
if ($search === '' || $search === []) {
177213
return $this;
178214
}
179215

180-
$pos = mb_strrpos($this->string, (string) $search);
216+
$farthestPosition = -1;
217+
218+
foreach (arr($search) as $char) {
219+
$position = mb_strrpos($this->string, $char);
181220

182-
if ($pos === false) {
221+
if ($position !== false && $position > $farthestPosition) {
222+
$farthestPosition = $position;
223+
}
224+
}
225+
226+
if ($farthestPosition === -1) {
183227
return $this;
184228
}
185229

186-
$string = mb_substr($this->string, start: 0, length: $pos);
230+
$string = mb_substr($this->string, start: 0, length: $farthestPosition);
187231

188232
return new self($string);
189233
}
@@ -231,4 +275,80 @@ public function endsWith(string $needle): bool
231275
{
232276
return str_ends_with($this->string, $needle);
233277
}
278+
279+
public function replaceFirst(string $search, string $replace): self
280+
{
281+
if ($search === '') {
282+
return $this;
283+
}
284+
285+
$position = strpos($this->string, $search);
286+
287+
if ($position === false) {
288+
return $this;
289+
}
290+
291+
return new self(substr_replace($this->string, $replace, $position, strlen($search)));
292+
}
293+
294+
public function replaceLast(string $search, string $replace): self
295+
{
296+
if ($search === '') {
297+
return $this;
298+
}
299+
300+
$position = strrpos($this->string, $search);
301+
302+
if ($position === false) {
303+
return $this;
304+
}
305+
306+
return new self(substr_replace($this->string, $replace, $position, strlen($search)));
307+
}
308+
309+
public function replaceEnd(string $search, string $replace): self
310+
{
311+
if ($search === '') {
312+
return $this;
313+
}
314+
315+
if (! $this->endsWith($search)) {
316+
return $this;
317+
}
318+
319+
return $this->replaceLast($search, $replace);
320+
}
321+
322+
public function replaceStart(string $search, string $replace): self
323+
{
324+
if ($search === '') {
325+
return $this;
326+
}
327+
328+
if (! $this->startsWith($search)) {
329+
return $this;
330+
}
331+
332+
return $this->replaceFirst($search, $replace);
333+
}
334+
335+
public function append(string ...$append): self
336+
{
337+
return new self($this->string . implode('', $append));
338+
}
339+
340+
public function prepend(string ...$prepend): self
341+
{
342+
return new self(implode('', $prepend) . $this->string);
343+
}
344+
345+
public function replace(string|array $search, string|array $replace): self
346+
{
347+
return new self(str_replace($search, $replace, $this->string));
348+
}
349+
350+
public function dd(mixed ...$dd): void
351+
{
352+
ld($this->string, ...$dd);
353+
}
234354
}

src/Tempest/Support/tests/StringHelperTest.php

Lines changed: 68 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,10 @@ public function test_str_after(): void
119119
$this->assertTrue(str('hannah')->after('xxxx')->equals('hannah'));
120120
$this->assertTrue(str('hannah')->after('')->equals('hannah'));
121121
$this->assertTrue(str('han0nah')->after('0')->equals('nah'));
122-
$this->assertTrue(str('han0nah')->after(0)->equals('nah'));
123-
$this->assertTrue(str('han2nah')->after(2)->equals('nah'));
122+
$this->assertTrue(str('han2nah')->after('2')->equals('nah'));
123+
$this->assertTrue(str('@[email protected]')->after(['@', '.'])->equals('[email protected]'));
124+
$this->assertTrue(str('[email protected]')->after(['@', '.'])->equals('bar.com'));
125+
$this->assertTrue(str('foobar.com')->after(['@', '.'])->equals('com'));
124126
}
125127

126128
public function test_str_after_last(): void
@@ -132,9 +134,9 @@ public function test_str_after_last(): void
132134
$this->assertTrue(str('yvette')->afterLast('xxxx')->equals('yvette'));
133135
$this->assertTrue(str('yvette')->afterLast('')->equals('yvette'));
134136
$this->assertTrue(str('yv0et0te')->afterLast('0')->equals('te'));
135-
$this->assertTrue(str('yv0et0te')->afterLast(0)->equals('te'));
136-
$this->assertTrue(str('yv2et2te')->afterLast(2)->equals('te'));
137+
$this->assertTrue(str('yv2et2te')->afterLast('2')->equals('te'));
137138
$this->assertTrue(str('----foo')->afterLast('---')->equals('foo'));
139+
$this->assertTrue(str('@[email protected]')->afterLast(['@', '.'])->equals('com'));
138140
}
139141

140142
public function test_str_between(): void
@@ -150,7 +152,7 @@ public function test_str_between(): void
150152
$this->assertTrue(str('[a]ab[b]')->between('[', ']')->equals('a]ab[b'));
151153
$this->assertTrue(str('foofoobar')->between('foo', 'bar')->equals('foo'));
152154
$this->assertTrue(str('foobarbar')->between('foo', 'bar')->equals('bar'));
153-
$this->assertTrue(str('12345')->between(1, 5)->equals('234'));
155+
$this->assertTrue(str('12345')->between('1', '5')->equals('234'));
154156
$this->assertTrue(str('123456789')->between('123', '6789')->equals('45'));
155157
$this->assertTrue(str('nothing')->between('foo', 'bar')->equals('nothing'));
156158
}
@@ -163,14 +165,15 @@ public function test_str_before(): void
163165
$this->assertTrue(str('hannah')->before('xxxx')->equals('hannah'));
164166
$this->assertTrue(str('hannah')->before('')->equals('hannah'));
165167
$this->assertTrue(str('han0nah')->before('0')->equals('han'));
166-
$this->assertTrue(str('han0nah')->before(0)->equals('han'));
167-
$this->assertTrue(str('han2nah')->before(2)->equals('han'));
168+
$this->assertTrue(str('han2nah')->before('2')->equals('han'));
168169
$this->assertTrue(str('')->before('')->equals(''));
169170
$this->assertTrue(str('')->before('a')->equals(''));
170171
$this->assertTrue(str('a')->before('a')->equals(''));
171172
$this->assertTrue(str('[email protected]')->before('@')->equals('foo'));
172173
$this->assertTrue(str('foo@@bar.com')->before('@')->equals('foo'));
173174
$this->assertTrue(str('@[email protected]')->before('@')->equals(''));
175+
$this->assertTrue(str('[email protected]')->before(['@', '.'])->equals('foo'));
176+
$this->assertTrue(str('@[email protected]')->before(['@', '.'])->equals(''));
174177
}
175178

176179
public function test_str_before_last(): void
@@ -182,12 +185,13 @@ public function test_str_before_last(): void
182185
$this->assertTrue(str('yvette')->beforeLast('xxxx')->equals('yvette'));
183186
$this->assertTrue(str('yvette')->beforeLast('')->equals('yvette'));
184187
$this->assertTrue(str('yv0et0te')->beforeLast('0')->equals('yv0et'));
185-
$this->assertTrue(str('yv0et0te')->beforeLast(0)->equals('yv0et'));
186-
$this->assertTrue(str('yv2et2te')->beforeLast(2)->equals('yv2et'));
188+
$this->assertTrue(str('yv2et2te')->beforeLast('2')->equals('yv2et'));
187189
$this->assertTrue(str('')->beforeLast('test')->equals(''));
188190
$this->assertTrue(str('yvette')->beforeLast('yvette')->equals(''));
189191
$this->assertTrue(str('tempest framework')->beforeLast(' ')->equals('tempest'));
190192
$this->assertTrue(str("yvette\tyv0et0te")->beforeLast("\t")->equals('yvette'));
193+
$this->assertTrue(str('This is Tempest.')->beforeLast([' ', '.'])->equals('This is Tempest'));
194+
$this->assertTrue(str('This is Tempest')->beforeLast([' ', '.'])->equals('This is'));
191195
}
192196

193197
public function test_starts_with(): void
@@ -201,4 +205,59 @@ public function test_ends_with(): void
201205
$this->assertTrue(str('abc')->endsWith('c'));
202206
$this->assertFalse(str('abc')->endsWith('a'));
203207
}
208+
209+
public function test_replace(): void
210+
{
211+
$this->assertTrue(str('foo bar')->replace('bar', 'baz')->equals('foo baz'));
212+
$this->assertTrue(str('jon doe')->replace(['jon', 'jane'], 'luke')->equals('luke doe'));
213+
$this->assertTrue(str('jon doe')->replace(['jon', 'jane', 'doe'], ['Jon', 'Jane', 'Doe'])->equals('Jon Doe'));
214+
$this->assertTrue(str('jon doe')->replace(['jon', 'jane', 'doe'], '<censored>')->equals('<censored> <censored>'));
215+
}
216+
217+
public function test_replace_last(): void
218+
{
219+
$this->assertTrue(str('foobar foobar')->replaceLast('bar', 'qux')->equals('foobar fooqux'));
220+
$this->assertTrue(str('foo/bar? foo/bar?')->replaceLast('bar?', 'qux?')->equals('foo/bar? foo/qux?'));
221+
$this->assertTrue(str('foobar foobar')->replaceLast('bar', '')->equals('foobar foo'));
222+
$this->assertTrue(str('foobar foobar')->replaceLast('xxx', 'yyy')->equals('foobar foobar'));
223+
$this->assertTrue(str('foobar foobar')->replaceLast('', 'yyy')->equals('foobar foobar'));
224+
$this->assertTrue(str('Malmö Jönköping')->replaceLast('ö', 'xxx')->equals('Malmö Jönkxxxping'));
225+
$this->assertTrue(str('Malmö Jönköping')->replaceLast('', 'yyy')->equals('Malmö Jönköping'));
226+
}
227+
228+
public function test_replace_first(): void
229+
{
230+
$this->assertTrue(str('foobar foobar')->replaceFirst('bar', 'qux')->equals('fooqux foobar'));
231+
$this->assertTrue(str('foo/bar? foo/bar?')->replaceFirst('bar?', 'qux?')->equals('foo/qux? foo/bar?'));
232+
$this->assertTrue(str('foobar foobar')->replaceFirst('bar', '')->equals('foo foobar'));
233+
$this->assertTrue(str('foobar foobar')->replaceFirst('xxx', 'yyy')->equals('foobar foobar'));
234+
$this->assertTrue(str('foobar foobar')->replaceFirst('', 'yyy')->equals('foobar foobar'));
235+
$this->assertTrue(str('Jönköping Malmö')->replaceFirst('ö', 'xxx')->equals('Jxxxnköping Malmö'));
236+
$this->assertTrue(str('Jönköping Malmö')->replaceFirst('', 'yyy')->equals('Jönköping Malmö'));
237+
238+
}
239+
240+
public function test_replace_end(): void
241+
{
242+
$this->assertTrue(str('foobar fooqux')->replaceEnd('bar', 'qux')->equals('foobar fooqux'));
243+
$this->assertTrue(str('foo/bar? foo/qux?')->replaceEnd('bar?', 'qux?')->equals('foo/bar? foo/qux?'));
244+
$this->assertTrue(str('foobar foo')->replaceEnd('bar', '')->equals('foobar foo'));
245+
$this->assertTrue(str('foobar foobar')->replaceEnd('xxx', 'yyy')->equals('foobar foobar'));
246+
$this->assertTrue(str('foobar foobar')->replaceEnd('', 'yyy')->equals('foobar foobar'));
247+
$this->assertTrue(str('fooxxx foobar')->replaceEnd('xxx', 'yyy')->equals('fooxxx foobar'));
248+
$this->assertTrue(str('Malmö Jönköping')->replaceEnd('ö', 'xxx')->equals('Malmö Jönköping'));
249+
$this->assertTrue(str('Malmö Jönköping')->replaceEnd('öping', 'yyy')->equals('Malmö Jönkyyy'));
250+
}
251+
252+
public function test_append(): void
253+
{
254+
$this->assertTrue(str('foo')->append('bar')->equals('foobar'));
255+
$this->assertTrue(str('foo')->append('bar', 'baz')->equals('foobarbaz'));
256+
}
257+
258+
public function test_prepend(): void
259+
{
260+
$this->assertTrue(str('bar')->prepend('foo')->equals('foobar'));
261+
$this->assertTrue(str('baz')->prepend('bar', 'foo')->equals('barfoobaz'));
262+
}
204263
}

0 commit comments

Comments
 (0)