├── .github ├── FUNDING.yml └── workflows │ ├── php-cs-fixer.yml │ └── run-tests.yml ├── .php-cs-fixer.dist.php ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json └── src ├── Ast ├── Ast.php ├── MultiNode.php ├── Node.php ├── SingleNode.php └── ToggleNode.php ├── QueryString.php └── StringHelper.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: https://spatie.be/open-source/support-us 2 | -------------------------------------------------------------------------------- /.github/workflows/php-cs-fixer.yml: -------------------------------------------------------------------------------- 1 | name: Check & fix styling 2 | 3 | on: [ push ] 4 | 5 | jobs: 6 | php-cs-fixer: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Checkout code 11 | uses: actions/checkout@v2 12 | with: 13 | ref: ${{ github.head_ref }} 14 | 15 | - name: Run PHP CS Fixer 16 | uses: docker://oskarstark/php-cs-fixer-ga 17 | with: 18 | args: --config=.php-cs-fixer.dist.php --allow-risky=yes 19 | 20 | - name: Commit changes 21 | uses: stefanzweifel/git-auto-commit-action@v4 22 | with: 23 | commit_message: Fix styling 24 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: run-tests 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: '0 0 * * *' 8 | 9 | jobs: 10 | test: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | php: [8.2, 8.1, 8.0, 7.4] 15 | dependency-version: [prefer-lowest, prefer-stable] 16 | os: [ubuntu-latest, windows-latest] 17 | 18 | name: P${{ matrix.php }} - ${{ matrix.dependency-version }} - ${{ matrix.os }} 19 | 20 | steps: 21 | - name: Checkout code 22 | uses: actions/checkout@v2 23 | 24 | - name: Cache dependencies 25 | uses: actions/cache@v2 26 | with: 27 | path: ~/.composer/cache/files 28 | key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} 29 | 30 | - name: Setup PHP 31 | uses: shivammathur/setup-php@v2 32 | with: 33 | php-version: ${{ matrix.php }} 34 | extensions: json, dom, curl, libxml, mbstring 35 | coverage: none 36 | 37 | - name: Install dependencies 38 | run: composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest 39 | 40 | - name: Configure matchers 41 | uses: mheap/phpunit-matcher-action@master 42 | 43 | - name: Execute tests 44 | run: vendor/bin/phpunit --teamcity 45 | -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | notPath('bootstrap/*') 5 | ->notPath('storage/*') 6 | ->notPath('resources/view/mail/*') 7 | ->in([ 8 | __DIR__ . '/src', 9 | __DIR__ . '/tests', 10 | ]) 11 | ->name('*.php') 12 | ->notName('*.blade.php') 13 | ->ignoreDotFiles(true) 14 | ->ignoreVCS(true); 15 | 16 | return (new PhpCsFixer\Config) 17 | ->setRules([ 18 | '@PSR2' => true, 19 | 'array_syntax' => ['syntax' => 'short'], 20 | 'ordered_imports' => ['sort_algorithm' => 'alpha'], 21 | 'no_unused_imports' => true, 22 | 'not_operator_with_successor_space' => true, 23 | 'trailing_comma_in_multiline' => true, 24 | 'phpdoc_scalar' => true, 25 | 'unary_operator_spaces' => true, 26 | 'binary_operator_spaces' => true, 27 | 'blank_line_before_statement' => [ 28 | 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], 29 | ], 30 | 'phpdoc_single_line_var_spacing' => true, 31 | 'phpdoc_var_without_name' => true, 32 | 'method_argument_space' => [ 33 | 'on_multiline' => 'ensure_fully_multiline', 34 | 'keep_multiple_spaces_after_comma' => true, 35 | ] 36 | ]) 37 | ->setFinder($finder); 38 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `query-filter` will be documented in this file 4 | 5 | ## 1.0.3 - 2020-11-30 6 | 7 | - add support for PHP 8 8 | 9 | ## 1.0.2 - 2020-11-30 10 | 11 | - query string 12 | 13 | ## 1.0.1 - 2019-11-15 14 | 15 | - Trim the `?` mark if no query filter is set 16 | 17 | ## 1.0.0 - 2019-02-07 18 | 19 | - initial release 20 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Spatie bvba 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [](https://supportukrainenow.org) 3 | 4 | # QueryString 5 | 6 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/query-string.svg?style=flat-square)](https://packagist.org/packages/spatie/query-string) 7 | ![run-tests](https://github.com/spatie/query-string/workflows/run-tests/badge.svg) 8 | [![Total Downloads](https://img.shields.io/packagist/dt/spatie/query-string.svg?style=flat-square)](https://packagist.org/packages/spatie/query-string) 9 | 10 | Work with query strings 11 | 12 | ## Support us 13 | 14 | [](https://spatie.be/github-ad-click/query-string) 15 | 16 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). 17 | 18 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). 19 | 20 | ## Installation 21 | 22 | You can install the package via composer: 23 | 24 | ```bash 25 | composer require spatie/query-string 26 | ``` 27 | 28 | ## Usage 29 | 30 | ``` php 31 | use Spatie\QueryString\QueryString; 32 | 33 | $queryString = new QueryString($uri); 34 | ``` 35 | 36 | ### Toggle parameters 37 | 38 | #### A single toggle 39 | 40 | ```php 41 | # / > /?toggle 42 | 43 | $queryString->toggle('toggle'); 44 | ``` 45 | 46 | #### Toggle a value 47 | 48 | ```php 49 | # / > /?single=a 50 | 51 | $queryString->toggle('single', 'a'); 52 | ``` 53 | 54 | ```php 55 | # /?single=a > /?single=b 56 | 57 | $queryString->toggle('single', 'b'); 58 | ``` 59 | 60 | ```php 61 | # /?single=a > /? 62 | 63 | $queryString->toggle('single', 'a'); 64 | ``` 65 | 66 | #### Toggle multiple values 67 | 68 | ```php 69 | # / > /?multi[]=a&multi[]=b 70 | 71 | $queryString->toggle('multi[]', 'a'); 72 | $queryString->toggle('multi[]', 'b'); 73 | ``` 74 | 75 | ```php 76 | # /?multi[]=a&multi[]=b > /?multi[]=a 77 | 78 | $queryString->toggle('multi[]', 'b'); 79 | ``` 80 | 81 | ### Filter 82 | 83 | Filtering the query string will use the JSON API filter syntax. 84 | 85 | ```php 86 | # / > /?filter[field]=a 87 | 88 | $queryString->filter('field', 'a'); 89 | ``` 90 | 91 | ```php 92 | # / > /?filter[field][]=b 93 | 94 | $queryString->filter('field[]', 'b'); 95 | ``` 96 | 97 | ### Sort 98 | 99 | Sorting the query string will use the JSON API sort syntax. 100 | At the moment only single sorts are supported. 101 | 102 | ```php 103 | # / > /?sort=field > /?sort=-field > /?sort=field 104 | 105 | $queryString->sort('field'); 106 | $queryString->sort('field'); 107 | $queryString->sort('field'); 108 | ``` 109 | 110 | ### Pagination 111 | 112 | There's built-in support for pagination: 113 | 114 | ```php 115 | $queryString->page(10); # /?page=10 116 | $queryString->nextPage(); # /?page=11 117 | $queryString->previousPage(); # /?page=9 118 | $queryString->resetPage(); # /? 119 | 120 | $queryString->isCurrentPage(1); # true 121 | ``` 122 | 123 | Note that changing any other value on the query string, will reset the page too. 124 | 125 | ### Other useful methods 126 | 127 | #### Base URL 128 | 129 | Casting a `QueryString` to a string will generate the URL. 130 | You can choose to use a different base URL like so: 131 | 132 | ```php 133 | $queryString->withBaseUrl('https://other.url'); 134 | ``` 135 | 136 | #### Clear a parameter 137 | 138 | ```php 139 | # /?toggle > / 140 | 141 | $queryString->clear('toggle'); 142 | ``` 143 | 144 | ```php 145 | # /?single=b > / 146 | 147 | $queryString->clear('single'); 148 | ``` 149 | 150 | ```php 151 | # /?multi[]=a&multi[]=b > / 152 | 153 | $queryString->clear('multi[]'); 154 | ``` 155 | 156 | #### Active parameter or not 157 | 158 | ```php 159 | # /?multi[]=a 160 | 161 | $queryString->isActive('multi[]'); # true 162 | $queryString->isActive('multi[]', 'a'); # true 163 | $queryString->isActive('multi[]', 'b'); # false 164 | ``` 165 | 166 | ```php 167 | # /?single=a 168 | 169 | $queryString->isActive('single'); # true 170 | $queryString->isActive('single', 'a'); # true 171 | $queryString->isActive('single', 'b'); # false 172 | ``` 173 | 174 | ```php 175 | # /?toggle 176 | 177 | $queryString->isActive('toggle'); # true 178 | ``` 179 | 180 | ### Laravel support 181 | 182 | A separate Laravel package will be added in the future. 183 | The Laravel package will use this one under the hood and implement the JSON API spec. 184 | 185 | ### Testing 186 | 187 | ``` bash 188 | composer test 189 | ``` 190 | 191 | ### Changelog 192 | 193 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 194 | 195 | ## Contributing 196 | 197 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. 198 | 199 | ### Security 200 | 201 | If you've found a bug regarding security please mail [security@spatie.be](mailto:security@spatie.be) instead of using the issue tracker. 202 | 203 | ## Postcardware 204 | 205 | You're free to use this package, but if it makes it to your production environment we highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. 206 | 207 | Our address is: Spatie, Kruikstraat 22, 2018 Antwerp, Belgium. 208 | 209 | We publish all received postcards [on our company website](https://spatie.be/en/opensource/postcards). 210 | 211 | ## Credits 212 | 213 | - [Brent Roose](https://github.com/brendt) 214 | - [All Contributors](../../contributors) 215 | 216 | ## License 217 | 218 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 219 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spatie/query-string", 3 | "description": "Manipulate query strings", 4 | "keywords": [ 5 | "spatie", 6 | "query-string" 7 | ], 8 | "homepage": "https://github.com/spatie/query-string", 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Brent Roose", 13 | "email": "brent@spatie.be", 14 | "homepage": "https://spatie.be", 15 | "role": "Developer" 16 | } 17 | ], 18 | "require": { 19 | "php": "^7.3|^8.0" 20 | }, 21 | "require-dev": { 22 | "phpunit/phpunit": "^9.4" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "Spatie\\QueryString\\": "src" 27 | } 28 | }, 29 | "autoload-dev": { 30 | "psr-4": { 31 | "Spatie\\QueryString\\Tests\\": "tests" 32 | } 33 | }, 34 | "scripts": { 35 | "test": "vendor/bin/phpunit", 36 | "test-coverage": "vendor/bin/phpunit --coverage-html coverage" 37 | }, 38 | "config": { 39 | "sort-packages": true 40 | }, 41 | "extra": {} 42 | } -------------------------------------------------------------------------------- /src/Ast/Ast.php: -------------------------------------------------------------------------------- 1 | buildNode($nodeDefinition); 26 | 27 | $this->addNode($node); 28 | } 29 | } 30 | 31 | public function __toString(): string 32 | { 33 | $nodes = $this->nodes; 34 | 35 | ksort($nodes); 36 | 37 | $parts = array_map('strval', $nodes); 38 | 39 | return implode('&', $parts); 40 | } 41 | 42 | public function add(string $name, ?string $value): Ast 43 | { 44 | $ast = clone $this; 45 | 46 | $definition = ! is_null($value) 47 | ? "{$name}={$value}" 48 | : $name; 49 | 50 | $node = $ast->buildNode($definition); 51 | 52 | $ast->addNode($node); 53 | 54 | return $ast; 55 | } 56 | 57 | public function remove(string $name, ?string $value): Ast 58 | { 59 | $ast = clone $this; 60 | 61 | if (! isset($ast[$name])) { 62 | return $ast; 63 | } 64 | 65 | $node = $ast[$name]; 66 | 67 | if ($value === null) { 68 | unset($ast[$name]); 69 | 70 | return $ast; 71 | } 72 | 73 | if ($node instanceof ToggleNode) { 74 | unset($ast[$name]); 75 | 76 | return $ast; 77 | } 78 | 79 | if ($node instanceof SingleNode && $node->value() === $value) { 80 | unset($ast[$name]); 81 | 82 | return $ast; 83 | } 84 | 85 | if ($node instanceof MultiNode) { 86 | $ast[$name] = $node->remove($value); 87 | 88 | return $ast; 89 | } 90 | 91 | return $ast; 92 | } 93 | 94 | public function offsetExists($offset): bool 95 | { 96 | return array_key_exists($offset, $this->nodes); 97 | } 98 | 99 | public function offsetGet($offset): Node 100 | { 101 | return $this->nodes[$offset]; 102 | } 103 | 104 | public function offsetSet($offset, $value): void 105 | { 106 | if (! $value instanceof Node) { 107 | throw new TypeError('Value must be instance of '.Node::class); 108 | } 109 | 110 | $this->nodes[$offset] = $value; 111 | } 112 | 113 | public function offsetUnset($offset): void 114 | { 115 | unset($this->nodes[$offset]); 116 | } 117 | 118 | private function buildNode(string $definition): Node 119 | { 120 | if (strpos($definition, '=') === false) { 121 | return new ToggleNode($definition); 122 | } 123 | 124 | [$name, $value] = explode('=', $definition); 125 | 126 | if (StringHelper::endsWith($name, '[]')) { 127 | return new MultiNode($name, (array) $value); 128 | } 129 | 130 | return new SingleNode($name, $value); 131 | } 132 | 133 | private function addNode(Node $node): void 134 | { 135 | if ($node instanceof MultiNode) { 136 | $this->addMultiNode($node); 137 | 138 | return; 139 | } 140 | 141 | $this->nodes[$node->name()] = $node; 142 | } 143 | 144 | private function addMultiNode(MultiNode $node): void 145 | { 146 | if (! isset($this->nodes[$node->name()])) { 147 | $this->nodes[$node->name()] = $node; 148 | 149 | return; 150 | } 151 | 152 | /** @var \Spatie\QueryString\Ast\MultiNode $existingNode */ 153 | $existingNode = $this->nodes[$node->name()]; 154 | 155 | $this->nodes[$node->name()] = $existingNode->merge($node); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/Ast/MultiNode.php: -------------------------------------------------------------------------------- 1 | name = $name; 18 | 19 | foreach ($values as $value) { 20 | $this->values[$value] = $value; 21 | } 22 | } 23 | 24 | public function name(): string 25 | { 26 | return $this->name; 27 | } 28 | 29 | public function values(): array 30 | { 31 | return $this->values; 32 | } 33 | 34 | public function merge(MultiNode $otherNode): MultiNode 35 | { 36 | $node = clone $this; 37 | 38 | foreach ($otherNode->values as $value) { 39 | $node->values[$value] = $value; 40 | } 41 | 42 | return $node; 43 | } 44 | 45 | public function remove(string $value): MultiNode 46 | { 47 | $node = clone $this; 48 | 49 | unset($node->values[$value]); 50 | 51 | return $node; 52 | } 53 | 54 | public function __toString(): string 55 | { 56 | $parts = []; 57 | 58 | foreach ($this->values as $value) { 59 | $parts[] = "{$this->name}={$value}"; 60 | } 61 | 62 | return implode('&', $parts); 63 | } 64 | 65 | public function isActive(?string $value): bool 66 | { 67 | if ($value === null) { 68 | return true; 69 | } 70 | 71 | return isset($this->values[$value]); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Ast/Node.php: -------------------------------------------------------------------------------- 1 | name = $name; 18 | $this->value = $value; 19 | } 20 | 21 | public function name(): string 22 | { 23 | return $this->name; 24 | } 25 | 26 | public function value(): string 27 | { 28 | return $this->value; 29 | } 30 | 31 | public function isActive(?string $value): bool 32 | { 33 | if ($value === null) { 34 | return true; 35 | } 36 | 37 | return $value === $this->value; 38 | } 39 | 40 | public function __toString(): string 41 | { 42 | return "{$this->name}={$this->value}"; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Ast/ToggleNode.php: -------------------------------------------------------------------------------- 1 | name = $name; 15 | } 16 | 17 | public function name(): string 18 | { 19 | return $this->name; 20 | } 21 | 22 | public function isActive(?string $value): bool 23 | { 24 | return true; 25 | } 26 | 27 | public function __toString(): string 28 | { 29 | return $this->name; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/QueryString.php: -------------------------------------------------------------------------------- 1 | baseUrl = $baseUrl; 39 | 40 | $this->ast = new Ast($query); 41 | } 42 | 43 | public function __toString() 44 | { 45 | return rtrim("{$this->baseUrl}?{$this->ast}", '?'); 46 | } 47 | 48 | public function withBaseUrl(string $baseUrl): QueryString 49 | { 50 | $queryString = clone $this; 51 | 52 | $queryString->baseUrl = $baseUrl; 53 | 54 | return $queryString; 55 | } 56 | 57 | public function isActive(string $name, $value = null): bool 58 | { 59 | if (! isset($this->ast[$name])) { 60 | return false; 61 | } 62 | 63 | return $this->ast[$name]->isActive($value); 64 | } 65 | 66 | public function toggle(string $name, $value = null): QueryString 67 | { 68 | return $this->isActive($name, $value) 69 | ? $this->disable($name, $value) 70 | : $this->enable($name, $value); 71 | } 72 | 73 | public function clear(string $name): QueryString 74 | { 75 | $queryString = clone $this; 76 | 77 | $ast = clone $this->ast; 78 | 79 | unset($ast[$name]); 80 | 81 | $queryString->ast = $ast; 82 | 83 | return $queryString; 84 | } 85 | 86 | public function enable(string $name, $value = null): QueryString 87 | { 88 | if (isset($this->defaults[$name]) && $this->defaults[$name] === $value) { 89 | return $this->disable($name); 90 | } 91 | 92 | $queryString = clone $this; 93 | 94 | if ($name !== 'page') { 95 | $queryString = $queryString->resetPage(); 96 | } 97 | 98 | $queryString->ast = $queryString->ast->add($name, $value); 99 | 100 | return $queryString; 101 | } 102 | 103 | public function disable(string $name, $value = null): QueryString 104 | { 105 | $queryString = clone $this; 106 | 107 | if ($name !== 'page') { 108 | $queryString = $queryString->resetPage(); 109 | } 110 | 111 | $queryString->ast = $queryString->ast->remove($name, $value); 112 | 113 | return $queryString; 114 | } 115 | 116 | public function default(string $name, $value): QueryString 117 | { 118 | $queryString = clone $this; 119 | 120 | $queryString->defaults[$name] = (string) $value; 121 | 122 | return $queryString; 123 | } 124 | 125 | public function filter(string $name, $value = null): QueryString 126 | { 127 | $filterName = $this->resolveFilterName($name); 128 | 129 | return $this->toggle($filterName, $value); 130 | } 131 | 132 | public function sort($value): QueryString 133 | { 134 | $value = $this->resolveSortValue($value); 135 | 136 | return $this->toggle($this->sortName, $value); 137 | } 138 | 139 | public function page(int $index): QueryString 140 | { 141 | return $this->enable('page', (string) $index); 142 | } 143 | 144 | public function nextPage(): QueryString 145 | { 146 | $index = $this->getCurrentPage() + 1; 147 | 148 | return $this->enable('page', (string) $index); 149 | } 150 | 151 | public function previousPage(): QueryString 152 | { 153 | $index = $this->getCurrentPage() - 1; 154 | 155 | if ($index < 1) { 156 | $index = 1; 157 | } 158 | 159 | return $this->enable('page', (string) $index); 160 | } 161 | 162 | public function resetPage(): QueryString 163 | { 164 | return $this->disable('page'); 165 | } 166 | 167 | public function getCurrentPage(): int 168 | { 169 | $index = 1; 170 | 171 | if (isset($this->ast['page']) && $this->ast['page'] instanceof SingleNode) { 172 | $index = $this->ast['page']->value(); 173 | } 174 | 175 | return (int) $index; 176 | } 177 | 178 | public function isCurrentPage(int $index): bool 179 | { 180 | return $this->getCurrentPage() === $index; 181 | } 182 | 183 | public function resolveFilterName($name): string 184 | { 185 | $isMultiple = StringHelper::endsWith($name, '[]'); 186 | 187 | if ($isMultiple) { 188 | $name = StringHelper::replaceLast('[]', '', $name); 189 | } 190 | 191 | if (strpos($name, "{$this->filterName}[") !== 0) { 192 | $name = "{$this->filterName}[{$name}]"; 193 | } 194 | 195 | if ($isMultiple) { 196 | $name .= '[]'; 197 | } 198 | 199 | return $name; 200 | } 201 | 202 | public function resolveSortValue($value): string 203 | { 204 | if (! $this->isActive($this->sortName, $value)) { 205 | return $value; 206 | } 207 | 208 | if (StringHelper::startsWith($value, '-')) { 209 | return substr($value, 1); 210 | } 211 | 212 | return "-{$value}"; 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/StringHelper.php: -------------------------------------------------------------------------------- 1 |