├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── config └── advanced-string.php ├── configure.php ├── resources └── views │ └── .gitkeep └── src ├── AdvStr.php ├── AdvStrServiceProvider.php ├── Facades └── AdvStr.php └── words.json /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `laravel-advanced-string` will be documented in this file. 4 | 5 | ## 1.1.1 - 2025-03-12 6 | 7 | ### What's Changed 8 | 9 | * Bump dependabot/fetch-metadata from 2.2.0 to 2.3.0 by @dependabot in https://github.com/mpstenson/laravel-advanced-string/pull/6 10 | * Bump aglipanci/laravel-pint-action from 2.4 to 2.5 by @dependabot in https://github.com/mpstenson/laravel-advanced-string/pull/7 11 | * Update composer.json Laravel v12 support by @rtaylor82 in https://github.com/mpstenson/laravel-advanced-string/pull/8 12 | 13 | ### New Contributors 14 | 15 | * @rtaylor82 made their first contribution in https://github.com/mpstenson/laravel-advanced-string/pull/8 16 | 17 | **Full Changelog**: https://github.com/mpstenson/laravel-advanced-string/compare/1.1.0...1.1.1 18 | 19 | ## 1.1.0 - 2024-08-25 20 | 21 | ### What's Changed 22 | 23 | * RandomWord and RandomPhrase functions by @mpstenson in https://github.com/mpstenson/laravel-advanced-string/pull/5 24 | 25 | **Full Changelog**: https://github.com/mpstenson/laravel-advanced-string/compare/1.0.1...1.1.0 26 | 27 | ## 1.0.1 - 2024-08-12 28 | 29 | ### What's Changed 30 | 31 | * fix issue #3 where config file would not publish by @mpstenson in https://github.com/mpstenson/laravel-advanced-string/pull/4 32 | 33 | **Full Changelog**: https://github.com/mpstenson/laravel-advanced-string/compare/1.0.0...1.0.1 34 | 35 | ## 1.0.0 - 2024-07-21 36 | 37 | ### What's Changed 38 | 39 | * Fix description of emailDomain function in README by @matthewpaulking in https://github.com/mpstenson/laravel-advanced-string/pull/2 40 | * Added credit card redaction functionality. 41 | 42 | Marking this as a 1.0.0. There will definitely be more added to this package but this is enough to get things going. 43 | 44 | ### New Contributors 45 | 46 | * @matthewpaulking made their first contribution in https://github.com/mpstenson/laravel-advanced-string/pull/2 47 | 48 | **Full Changelog**: https://github.com/mpstenson/laravel-advanced-string/compare/0.0.3...1.0.0 49 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Matt Stenson 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 | # Laravel Advanced String Package 2 | *Tested, community maintained, supercharged Laravel string functions.* 3 | 4 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/mpstenson/laravel-advanced-string.svg?style=flat-square)](https://packagist.org/packages/mpstenson/laravel-advanced-string) 5 | [![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/mpstenson/laravel-advanced-string/run-tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/mpstenson/laravel-advanced-string/actions?query=workflow%3Arun-tests+branch%3Amain) 6 | [![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/mpstenson/laravel-advanced-string/fix-php-code-style-issues.yml?branch=main&label=code%20style&style=flat-square)](https://github.com/mpstenson/laravel-advanced-string/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amain) 7 | [![Total Downloads](https://img.shields.io/packagist/dt/mpstenson/laravel-advanced-string.svg?style=flat-square)](https://packagist.org/packages/mpstenson/laravel-advanced-string) 8 | 9 | Laravel Advanced String is a Laravel package that adds advanced string manipulation methods to the built in `Str` class that Laravel provides. You get extended functionality on strings such as advanced password generation, data redaction, and more. 10 | 11 | The Laravel Advanced String package by default adds macros to the `Str` class so your can access the extended functionality in the same class that your other string methods are found in. You can also disable this functionality the in the package config 12 | and use the `AdvStr` class directly. 13 | 14 | ### Example 15 | 16 | 17 | https://github.com/user-attachments/assets/ad4eb551-f383-4ed7-b9db-c44823e637c8 18 | 19 | 20 | ```php 21 | Str::redactSsn('My social security number is 222-22-2222'); // My social security number is xxxxxx 22 | ``` 23 | OR... 24 | ```php 25 | AdvStr::redactSsn('My social security number is 222-22-2222'); // My social security number is xxxxxx 26 | ``` 27 | 28 | ## Table of Contents 29 | - [Installation](#installation) 30 | - [Usage](#usage) 31 | - [Available Methods](#available-methods) 32 | - [advPassword](#advpassword) 33 | - [charWrap](#charwrap) 34 | - [emailDomain](#emaildomain) 35 | - [randomPhrase](#randomphrase) 36 | - [randomWord](#randomword) 37 | - [readTime](#readtime) 38 | - [redactCreditCard](#redactcreditcard) 39 | - [redactSsn](#redactssn) 40 | - [splitName](#splitname) 41 | - [Testing](#testing) 42 | - [Changelog](#changelog) 43 | - [Credits](#credits) 44 | - [License](#license) 45 | 46 | ## Installation 47 | 48 | You can install the package via composer: 49 | 50 | ```bash 51 | composer require mpstenson/laravel-advanced-string 52 | ``` 53 | 54 | You can publish the config file with: 55 | 56 | ```bash 57 | php artisan vendor:publish --tag="laravel-advanced-string-config" 58 | ``` 59 | 60 | This is the contents of the published config file: 61 | 62 | ```php 63 | return [ 64 | 65 | /* 66 | // Macro the AdvStr class to the Illuminate\Support\Str class. You can disable 67 | // this here if you don't want the AdvStr methods available on the Str class 68 | */ 69 | 70 | 'use_str' => true, 71 | 72 | ]; 73 | ``` 74 | 75 | ## Usage 76 | The Laravel Advanced String package by default adds macros to the `Str` class so your can access the extended functionality immediately 77 | ```php 78 | Str::redactSsn('123-45-6789') 79 | ``` 80 | 81 | ## Available Methods 82 | 83 | ### [advPassword](#advpassword) 84 | 85 | Generates a random, secure password. 86 | 87 | ```php 88 | public static function advPassword( 89 | $length = 32, 90 | $letters = true, 91 | $numbers = true, 92 | $symbols = true, 93 | $spaces = false, 94 | $upperLetters = false, 95 | $lowerLetters = false, 96 | $exclude = [] 97 | ) 98 | ``` 99 | 100 | #### Parameters: 101 | - `$length` (int): Length of the password (default: 32) 102 | - `$letters` (bool): Include mixed case letters (default: true) 103 | - `$numbers` (bool): Include numbers (default: true) 104 | - `$symbols` (bool): Include symbols (default: true) 105 | - `$spaces` (bool): Include spaces (default: false) 106 | - `$upperLetters` (bool): Include uppercase letters (default: false) 107 | - `$lowerLetters` (bool): Include lowercase letters (default: false) 108 | - `$exclude` (array): Characters to exclude from the password 109 | 110 | #### Returns: 111 | - string: Generated password 112 | 113 | ### [charWrap](#charwrap) 114 | 115 | Wraps a string at a given number of characters regardless of words. 116 | 117 | ```php 118 | public static function charWrap( 119 | $string, 120 | $length = 80 121 | ) 122 | ``` 123 | 124 | #### Parameters: 125 | - `$string` (string): The string to wrap 126 | - `$length` (int): The number of characters to wrap at (default: 80) 127 | 128 | #### Returns: 129 | - string: The wrapped string 130 | 131 | ### [emailDomain](#emailDomain) 132 | 133 | Extracts the domain part of an email address, including subdomains. 134 | 135 | ```php 136 | public static function emailDomain( 137 | $string 138 | ) 139 | ``` 140 | 141 | #### Parameters: 142 | - `$string` (string): The string to extract the email domain from. 143 | 144 | #### Returns: 145 | - string: The email domain from the string 146 | 147 | ### [randomPhrase](#randomphrase) 148 | 149 | Returns a random phrase with a configurable delimiter. 150 | 151 | ```php 152 | public static function randomPhrase( 153 | $wordCount, 154 | $separator = '-' 155 | ) 156 | ``` 157 | 158 | #### Parameters: 159 | - `$wordCount` (int): The number of words in the phrase. 160 | - `$separator` (string): The separator between words (default: '-'). 161 | 162 | #### Returns: 163 | - string: The generated random phrase. 164 | ### [randomWord](#randomword) 165 | 166 | Returns a random word. 167 | 168 | ```php 169 | public static function randomWord( 170 | ) 171 | ``` 172 | 173 | #### Parameters: 174 | - none 175 | 176 | #### Returns: 177 | - string: A random word 178 | 179 | ### [readTime](#readtime) 180 | 181 | Calculates the read time of a string. 182 | 183 | ```php 184 | public static function readTime( 185 | $string, 186 | $wpm = 200 187 | ) 188 | ``` 189 | 190 | #### Parameters: 191 | - `$string` (string): The text to calculate read time for 192 | - `$wpm` (int): Words per minute (default: 200) 193 | 194 | #### Returns: 195 | - float: Estimated read time in seconds 196 | 197 | ### [redactCreditCard](#redactcreditcard) 198 | Redacts credit card numbers in a string. 199 | 200 | ```php 201 | public static function redactCreditCard( 202 | $string, 203 | $redacted = '********', 204 | $exclude = [] 205 | ) 206 | ``` 207 | 208 | #### Parameters: 209 | - `$string` (string): The string containing credit card numbers to redact 210 | - `$redacted` (string): The string to replace credit card numbers with (default: '********') 211 | - `$exclude` (array): An array of credit card types to exclude from redaction. Possible types: 'mastercard','visa','amex','discover','diners','jcb' 212 | 213 | #### Returns: 214 | - string: The string with credit card numbers redacted 215 | ### [redactSsn](#redactssn) 216 | 217 | Redacts Social Security Numbers (SSN) in a string. 218 | 219 | ```php 220 | public static function redactSsn( 221 | $string, 222 | $redacted = '********', 223 | $dashes = true, 224 | $noDashes = true 225 | ) 226 | ``` 227 | 228 | #### Parameters: 229 | - `$string` (string): The string containing SSNs to redact 230 | - `$redacted` (string): The string to replace SSNs with (default: '********') 231 | - `$dashes` (bool): Redact SSNs with dashes (default: true) 232 | - `$noDashes` (bool): Redact SSNs without dashes (default: true) 233 | 234 | #### Returns: 235 | - string: The string with SSNs redacted 236 | 237 | ### [splitName](#splitname) 238 | 239 | Splits a full name into first name, middle name (if present), and last name, removing any prefixes and suffixes. This method can handle both "Firstname Lastname" and "Lastname, Firstname" formats. 240 | 241 | ```php 242 | public static function splitName( 243 | $name 244 | ) 245 | ``` 246 | 247 | #### Parameters: 248 | - `$name` (string): The full name to split 249 | 250 | #### Returns: 251 | - array: An associative array containing 'first', 'middle' (if present), and 'last' name 252 | 253 | 254 | ## Testing 255 | 256 | ```bash 257 | composer test 258 | ``` 259 | 260 | ## Changelog 261 | 262 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 263 | 264 | ## Credits 265 | 266 | - [Matt Stenson](https://github.com/mpstenson) 267 | - [All Contributors](../../contributors) 268 | 269 | ## License 270 | 271 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 272 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mpstenson/laravel-advanced-string", 3 | "description": "This application provides advanced string functions beyond what Laravel itself provides.", 4 | "keywords": [ 5 | "Matt Stenson", 6 | "laravel", 7 | "laravel-advanced-string" 8 | ], 9 | "homepage": "https://github.com/mpstenson/laravel-advanced-string", 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Matt Stenson", 14 | "email": "matt.stenson@gmail.com", 15 | "role": "Developer" 16 | } 17 | ], 18 | "require": { 19 | "php": "^8.2", 20 | "spatie/laravel-package-tools": "^1.19", 21 | "illuminate/contracts": "^10.0||^11.0||^12.0" 22 | }, 23 | "require-dev": { 24 | "laravel/pint": "^1.14", 25 | "nunomaduro/collision": "^8.1.1||^7.10.0", 26 | "larastan/larastan": "^2.9", 27 | "orchestra/testbench": "^9.0.0||^8.22.0", 28 | "pestphp/pest": "^2.34", 29 | "pestphp/pest-plugin-arch": "^2.7", 30 | "pestphp/pest-plugin-laravel": "^2.3", 31 | "phpstan/extension-installer": "^1.3", 32 | "phpstan/phpstan-deprecation-rules": "^1.1", 33 | "phpstan/phpstan-phpunit": "^1.3" 34 | }, 35 | "autoload": { 36 | "psr-4": { 37 | "mpstenson\\AdvStr\\": "src/", 38 | "mpstenson\\AdvStr\\Database\\Factories\\": "database/factories/" 39 | } 40 | }, 41 | "autoload-dev": { 42 | "psr-4": { 43 | "mpstenson\\AdvStr\\Tests\\": "tests/", 44 | "Workbench\\App\\": "workbench/app/" 45 | } 46 | }, 47 | "scripts": { 48 | "post-autoload-dump": "@composer run prepare", 49 | "clear": "@php vendor/bin/testbench package:purge-laravel-advanced-string --ansi", 50 | "prepare": "@php vendor/bin/testbench package:discover --ansi", 51 | "build": [ 52 | "@composer run prepare", 53 | "@php vendor/bin/testbench workbench:build --ansi" 54 | ], 55 | "start": [ 56 | "Composer\\Config::disableProcessTimeout", 57 | "@composer run build", 58 | "@php vendor/bin/testbench serve" 59 | ], 60 | "analyse": "vendor/bin/phpstan analyse", 61 | "test": "vendor/bin/pest", 62 | "test-coverage": "vendor/bin/pest --coverage", 63 | "format": "vendor/bin/pint" 64 | }, 65 | "config": { 66 | "sort-packages": true, 67 | "allow-plugins": { 68 | "pestphp/pest-plugin": true, 69 | "phpstan/extension-installer": true 70 | } 71 | }, 72 | "extra": { 73 | "laravel": { 74 | "providers": [ 75 | "mpstenson\\AdvStr\\AdvStrServiceProvider" 76 | ], 77 | "aliases": { 78 | "AdvStr": "mpstenson\\AdvStr\\Facades\\AdvStr" 79 | } 80 | } 81 | }, 82 | "minimum-stability": "dev", 83 | "prefer-stable": true 84 | } 85 | -------------------------------------------------------------------------------- /config/advanced-string.php: -------------------------------------------------------------------------------- 1 | true, 12 | 13 | ]; 14 | -------------------------------------------------------------------------------- /configure.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | $version) { 90 | if (in_array($name, $names, true)) { 91 | unset($data['require-dev'][$name]); 92 | } 93 | } 94 | 95 | file_put_contents(__DIR__.'/composer.json', json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); 96 | } 97 | 98 | function remove_composer_script($scriptName) 99 | { 100 | $data = json_decode(file_get_contents(__DIR__.'/composer.json'), true); 101 | 102 | foreach ($data['scripts'] as $name => $script) { 103 | if ($scriptName === $name) { 104 | unset($data['scripts'][$name]); 105 | break; 106 | } 107 | } 108 | 109 | file_put_contents(__DIR__.'/composer.json', json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); 110 | } 111 | 112 | function remove_readme_paragraphs(string $file): void 113 | { 114 | $contents = file_get_contents($file); 115 | 116 | file_put_contents( 117 | $file, 118 | preg_replace('/.*/s', '', $contents) ?: $contents 119 | ); 120 | } 121 | 122 | function safeUnlink(string $filename) 123 | { 124 | if (file_exists($filename) && is_file($filename)) { 125 | unlink($filename); 126 | } 127 | } 128 | 129 | function determineSeparator(string $path): string 130 | { 131 | return str_replace('/', DIRECTORY_SEPARATOR, $path); 132 | } 133 | 134 | function replaceForWindows(): array 135 | { 136 | return preg_split('/\\r\\n|\\r|\\n/', run('dir /S /B * | findstr /v /i .git\ | findstr /v /i vendor | findstr /v /i '.basename(__FILE__).' | findstr /r /i /M /F:/ ":author :vendor :package VendorName skeleton migration_table_name vendor_name vendor_slug author@domain.com"')); 137 | } 138 | 139 | function replaceForAllOtherOSes(): array 140 | { 141 | return explode(PHP_EOL, run('grep -E -r -l -i ":author|:vendor|:package|VendorName|skeleton|migration_table_name|vendor_name|vendor_slug|author@domain.com" --exclude-dir=vendor ./* ./.github/* | grep -v '.basename(__FILE__))); 142 | } 143 | 144 | function getGitHubApiEndpoint(string $endpoint): ?stdClass 145 | { 146 | try { 147 | $curl = curl_init("https://api.github.com/{$endpoint}"); 148 | curl_setopt_array($curl, [ 149 | CURLOPT_RETURNTRANSFER => true, 150 | CURLOPT_FOLLOWLOCATION => true, 151 | CURLOPT_HTTPGET => true, 152 | CURLOPT_HTTPHEADER => [ 153 | 'User-Agent: spatie-configure-script/1.0', 154 | ], 155 | ]); 156 | 157 | $response = curl_exec($curl); 158 | $statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); 159 | 160 | curl_close($curl); 161 | 162 | if ($statusCode === 200) { 163 | return json_decode($response); 164 | } 165 | } catch (Exception $e) { 166 | // ignore 167 | } 168 | 169 | return null; 170 | } 171 | 172 | function searchCommitsForGitHubUsername(): string 173 | { 174 | $authorName = strtolower(trim(shell_exec('git config user.name'))); 175 | 176 | $committersRaw = shell_exec("git log --author='@users.noreply.github.com' --pretty='%an:%ae' --reverse"); 177 | $committersLines = explode("\n", $committersRaw ?? ''); 178 | $committers = array_filter(array_map(function ($line) use ($authorName) { 179 | $line = trim($line); 180 | [$name, $email] = explode(':', $line) + [null, null]; 181 | 182 | return [ 183 | 'name' => $name, 184 | 'email' => $email, 185 | 'isMatch' => strtolower($name) === $authorName && ! str_contains($name, '[bot]'), 186 | ]; 187 | }, $committersLines), fn ($item) => $item['isMatch']); 188 | 189 | if (empty($committers)) { 190 | return ''; 191 | } 192 | 193 | $firstCommitter = reset($committers); 194 | 195 | return explode('@', $firstCommitter['email'])[0] ?? ''; 196 | } 197 | 198 | function guessGitHubUsernameUsingCli() 199 | { 200 | try { 201 | if (preg_match('/ogged in to github\.com as ([a-zA-Z-_]+).+/', shell_exec('gh auth status -h github.com 2>&1'), $matches)) { 202 | return $matches[1]; 203 | } 204 | } catch (Exception $e) { 205 | // ignore 206 | } 207 | 208 | return ''; 209 | } 210 | 211 | function guessGitHubUsername(): string 212 | { 213 | $username = searchCommitsForGitHubUsername(); 214 | if (! empty($username)) { 215 | return $username; 216 | } 217 | 218 | $username = guessGitHubUsernameUsingCli(); 219 | if (! empty($username)) { 220 | return $username; 221 | } 222 | 223 | // fall back to using the username from the git remote 224 | $remoteUrl = shell_exec('git config remote.origin.url'); 225 | $remoteUrlParts = explode('/', str_replace(':', '/', trim($remoteUrl))); 226 | 227 | return $remoteUrlParts[1] ?? ''; 228 | } 229 | 230 | function guessGitHubVendorInfo($authorName, $username): array 231 | { 232 | $remoteUrl = shell_exec('git config remote.origin.url'); 233 | $remoteUrlParts = explode('/', str_replace(':', '/', trim($remoteUrl))); 234 | 235 | $response = getGitHubApiEndpoint("orgs/{$remoteUrlParts[1]}"); 236 | 237 | if ($response === null) { 238 | return [$authorName, $username]; 239 | } 240 | 241 | return [$response->name ?? $authorName, $response->login ?? $username]; 242 | } 243 | 244 | $gitName = run('git config user.name'); 245 | $authorName = ask('Author name', $gitName); 246 | 247 | $gitEmail = run('git config user.email'); 248 | $authorEmail = ask('Author email', $gitEmail); 249 | $authorUsername = ask('Author username', guessGitHubUsername()); 250 | 251 | $guessGitHubVendorInfo = guessGitHubVendorInfo($authorName, $authorUsername); 252 | 253 | $vendorName = ask('Vendor name', $guessGitHubVendorInfo[0]); 254 | $vendorUsername = ask('Vendor username', $guessGitHubVendorInfo[1] ?? slugify($vendorName)); 255 | $vendorSlug = slugify($vendorUsername); 256 | 257 | $vendorNamespace = str_replace('-', '', ucwords($vendorName)); 258 | $vendorNamespace = ask('Vendor namespace', $vendorNamespace); 259 | 260 | $currentDirectory = getcwd(); 261 | $folderName = basename($currentDirectory); 262 | 263 | $packageName = ask('Package name', $folderName); 264 | $packageSlug = slugify($packageName); 265 | $packageSlugWithoutPrefix = remove_prefix('laravel-', $packageSlug); 266 | 267 | $className = title_case($packageName); 268 | $className = ask('Class name', $className); 269 | $variableName = lcfirst($className); 270 | $description = ask('Package description', "This is my package {$packageSlug}"); 271 | 272 | $usePhpStan = confirm('Enable PhpStan?', true); 273 | $useLaravelPint = confirm('Enable Laravel Pint?', true); 274 | $useDependabot = confirm('Enable Dependabot?', true); 275 | $useLaravelRay = confirm('Use Ray for debugging?', true); 276 | $useUpdateChangelogWorkflow = confirm('Use automatic changelog updater workflow?', true); 277 | 278 | writeln('------'); 279 | writeln("Author : {$authorName} ({$authorUsername}, {$authorEmail})"); 280 | writeln("Vendor : {$vendorName} ({$vendorSlug})"); 281 | writeln("Package : {$packageSlug} <{$description}>"); 282 | writeln("Namespace : {$vendorNamespace}\\{$className}"); 283 | writeln("Class name : {$className}"); 284 | writeln('---'); 285 | writeln('Packages & Utilities'); 286 | writeln('Use Laravel/Pint : '.($useLaravelPint ? 'yes' : 'no')); 287 | writeln('Use Larastan/PhpStan : '.($usePhpStan ? 'yes' : 'no')); 288 | writeln('Use Dependabot : '.($useDependabot ? 'yes' : 'no')); 289 | writeln('Use Ray App : '.($useLaravelRay ? 'yes' : 'no')); 290 | writeln('Use Auto-Changelog : '.($useUpdateChangelogWorkflow ? 'yes' : 'no')); 291 | writeln('------'); 292 | 293 | writeln('This script will replace the above values in all relevant files in the project directory.'); 294 | 295 | if (! confirm('Modify files?', true)) { 296 | exit(1); 297 | } 298 | 299 | $files = (str_starts_with(strtoupper(PHP_OS), 'WIN') ? replaceForWindows() : replaceForAllOtherOSes()); 300 | 301 | foreach ($files as $file) { 302 | replace_in_file($file, [ 303 | ':author_name' => $authorName, 304 | ':author_username' => $authorUsername, 305 | 'author@domain.com' => $authorEmail, 306 | ':vendor_name' => $vendorName, 307 | ':vendor_slug' => $vendorSlug, 308 | 'VendorName' => $vendorNamespace, 309 | ':package_name' => $packageName, 310 | ':package_slug' => $packageSlug, 311 | ':package_slug_without_prefix' => $packageSlugWithoutPrefix, 312 | 'Skeleton' => $className, 313 | 'skeleton' => $packageSlug, 314 | 'migration_table_name' => title_snake($packageSlug), 315 | 'variable' => $variableName, 316 | ':package_description' => $description, 317 | ]); 318 | 319 | match (true) { 320 | str_contains($file, determineSeparator('src/Skeleton.php')) => rename($file, determineSeparator('./src/'.$className.'.php')), 321 | str_contains($file, determineSeparator('src/SkeletonServiceProvider.php')) => rename($file, determineSeparator('./src/'.$className.'ServiceProvider.php')), 322 | str_contains($file, determineSeparator('src/Facades/Skeleton.php')) => rename($file, determineSeparator('./src/Facades/'.$className.'.php')), 323 | str_contains($file, determineSeparator('src/Commands/SkeletonCommand.php')) => rename($file, determineSeparator('./src/Commands/'.$className.'Command.php')), 324 | str_contains($file, determineSeparator('database/migrations/create_skeleton_table.php.stub')) => rename($file, determineSeparator('./database/migrations/create_'.title_snake($packageSlugWithoutPrefix).'_table.php.stub')), 325 | str_contains($file, determineSeparator('config/skeleton.php')) => rename($file, determineSeparator('./config/'.$packageSlugWithoutPrefix.'.php')), 326 | str_contains($file, 'README.md') => remove_readme_paragraphs($file), 327 | default => [], 328 | }; 329 | } 330 | 331 | if (! $useLaravelPint) { 332 | safeUnlink(__DIR__.'/.github/workflows/fix-php-code-style-issues.yml'); 333 | safeUnlink(__DIR__.'/pint.json'); 334 | } 335 | 336 | if (! $usePhpStan) { 337 | safeUnlink(__DIR__.'/phpstan.neon.dist'); 338 | safeUnlink(__DIR__.'/phpstan-baseline.neon'); 339 | safeUnlink(__DIR__.'/.github/workflows/phpstan.yml'); 340 | 341 | remove_composer_deps([ 342 | 'phpstan/extension-installer', 343 | 'phpstan/phpstan-deprecation-rules', 344 | 'phpstan/phpstan-phpunit', 345 | 'larastan/larastan', 346 | ]); 347 | 348 | remove_composer_script('phpstan'); 349 | } 350 | 351 | if (! $useDependabot) { 352 | safeUnlink(__DIR__.'/.github/dependabot.yml'); 353 | safeUnlink(__DIR__.'/.github/workflows/dependabot-auto-merge.yml'); 354 | } 355 | 356 | if (! $useLaravelRay) { 357 | remove_composer_deps(['spatie/laravel-ray']); 358 | } 359 | 360 | if (! $useUpdateChangelogWorkflow) { 361 | safeUnlink(__DIR__.'/.github/workflows/update-changelog.yml'); 362 | } 363 | 364 | confirm('Execute `composer install` and run tests?') && run('composer install && composer test'); 365 | 366 | confirm('Let this script delete itself?', true) && unlink(__FILE__); 367 | -------------------------------------------------------------------------------- /resources/views/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpstenson/laravel-advanced-string/d6536520e3da18e76e0723e6480d49bb14bb34a0/resources/views/.gitkeep -------------------------------------------------------------------------------- /src/AdvStr.php: -------------------------------------------------------------------------------- 1 | $upperLetters === true ? [ 36 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 37 | 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 38 | 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 39 | ] : null, 40 | 'lowerLetters' => $lowerLetters === true ? [ 41 | 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 42 | 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 43 | 'w', 'x', 'y', 'z', 44 | ] : null, 45 | 'numbers' => $numbers === true ? [ 46 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 47 | ] : null, 48 | 'symbols' => $symbols === true ? [ 49 | '~', '!', '#', '$', '%', '^', '&', '*', '(', ')', '-', 50 | '_', '.', ',', '<', '>', '?', '/', '\\', '{', '}', '[', 51 | ']', '|', ':', ';', 52 | ] : null, 53 | 'spaces' => $spaces === true ? [' '] : null, 54 | ]))->filter(); 55 | 56 | // Remove excluded characters 57 | $options = $options->map(function ($chars) use ($exclude) { 58 | return array_values(array_diff($chars, $exclude)); 59 | })->filter(function ($chars) { 60 | return ! empty($chars); 61 | }); 62 | 63 | $options->each(fn ($c) => $password->push($c[random_int(0, count($c) - 1)])); 64 | // add the remaining characters to the password 65 | 66 | // Calculate remaining length 67 | $remainingLength = $length - $password->count(); 68 | 69 | // Generate remaining characters 70 | $allChars = $options->flatten()->toArray(); 71 | for ($i = 0; $i < $remainingLength; $i++) { 72 | $password->push($allChars[random_int(0, count($allChars) - 1)]); 73 | } 74 | 75 | return $password->shuffle()->implode(''); 76 | } 77 | 78 | /** 79 | * Calculate the read time of a string. 80 | * 81 | * @param string $string 82 | * @param int $wpm 83 | * @return float 84 | */ 85 | public static function readTime($string, $wpm = 200) 86 | { 87 | /** 88 | * Silent-reading adults average 238 words per minute. 89 | * Adults who read aloud average 183 words per minute. 90 | * We average to 200 words per minute. 91 | */ 92 | 93 | // Calculate the number of words in the string 94 | $word_count = str_word_count($string); 95 | 96 | // Calculate words per second 97 | $wps = $wpm / 60; 98 | 99 | // Calculate the number of seconds it takes to read the string 100 | $seconds = $word_count / $wps; 101 | 102 | // Return the reading time in seconds 103 | return round($seconds); 104 | } 105 | 106 | /** 107 | * Wrap a string at a given number of characters regardless of words. 108 | * 109 | * @param string $string The string to wrap. 110 | * @param int $length The number of characters to wrap at. 111 | * @return string The wrapped string. 112 | */ 113 | public static function charWrap($string, $length = 80) 114 | { 115 | // split the string into an array of string x characters long 116 | $string = str_split($string, $length); 117 | $string = implode("\n", $string); 118 | 119 | return $string; 120 | } 121 | 122 | /** 123 | * First name last name splitter, returns an array of first and last name 124 | * and removes any prefixes 125 | * 126 | * @param string $name 127 | * @return array 128 | */ 129 | public static function splitName($name) 130 | { 131 | $prefixes = [ // Titles with periods 132 | 'Mr.', 133 | 'Mrs.', 134 | 'Ms.', 135 | 'Mx.', // Mx. is a gender-neutral prefix 136 | 'Dr.', 137 | 'Prof.', 138 | 'Rev.', // Reverend 139 | 'Sr.', // Sister (religious) 140 | 'Fr.', // Father (religious) 141 | 'Capt.', // Captain 142 | 'Lt.', // Lieutenant 143 | 'Col.', // Colonel 144 | 'Gen.', // General 145 | 'Maj.', // Major 146 | 'Sgt.', // Sergeant 147 | 'Lord', 148 | 'Lady', 149 | 'Sir', 150 | 'Saint', 151 | 152 | // Titles without periods 153 | 'Doctor', 154 | 'Professor', 155 | 'Reverend', 156 | 'Rev', 157 | 'Sister', 158 | 'Father', 159 | 'Captain', 160 | 'Lieutenant', 161 | 'Colonel', 162 | 'General', 163 | 'Major', 164 | 'Sergeant', 165 | 'Madame', // French for Ms. 166 | 'Mademoiselle', // French for Miss (unmarried woman) 167 | 'Monsignor', // Catholic title 168 | 'Rabbi', 169 | 'Imam', 170 | 'Sheikh', 171 | 'Master', // Used for some professions (e.g., Master Sergeant) 172 | 'Miss', // Traditionally used for unmarried women (less common now) 173 | 'Mister', // Less common variation of Mr. 174 | 'Mx', // Non-period version of Mx. 175 | 'Mr', // Non-period version of Mr. 176 | 'Mrs', // Non-period version of Mrs. 177 | 'Ms', // Non-period version of Ms. 178 | 'Coach', 179 | 'Dr.', // Can be used informally for medical doctors 180 | 'Prof.', // Can be used informally for professors 181 | 'Ms.', // Can be used informally for women 182 | ]; 183 | 184 | $suffixes = [ 185 | 'PhD', 186 | 'MD', 187 | 'DDS', 188 | 'DVM', 189 | 'Ph.D.', 190 | 'Jr.', 191 | 'Sr.', 192 | 'I', 193 | 'II', 194 | 'III', 195 | 'IV', 196 | 'V', 197 | 'VI', 198 | 'VII', 199 | 'VIII', 200 | 'IX', 201 | 'X', 202 | ]; 203 | 204 | // Add lower case version of the prefixes while keeping the original versions 205 | $prefixes = array_merge($prefixes, array_map('strtolower', $prefixes), array_map('strtoupper', $prefixes)); 206 | $suffixes = array_merge($suffixes, array_map('strtolower', $suffixes), array_map('strtoupper', $suffixes)); 207 | 208 | // Remove any matching prefixes 209 | foreach ($prefixes as $prefix) { 210 | if (stripos($name, $prefix) === 0) { // Check if the prefix is at the beginning 211 | // Remove the prefix 212 | $name = trim(substr($name, strlen($prefix))); 213 | } 214 | } 215 | 216 | //if name has commas assume it is in the order last, first 217 | if (strpos($name, ',') !== false) { 218 | $name = explode(',', $name); 219 | 220 | return [ 221 | 'first' => trim($name[1]), 222 | 'last' => trim($name[0]), 223 | ]; 224 | } else { 225 | $name = explode(' ', $name); 226 | if (count($name) > 2) { 227 | // remove any suffixes 228 | $name = array_diff($name, $suffixes); 229 | 230 | return [ 231 | // get the first 232 | 'first' => $name[0], 233 | //middle names will be any array items between the first and last names 234 | 'middle' => implode(' ', array_slice($name, 1, count($name) - 2)), 235 | 'last' => $name[count($name) - 1], 236 | ]; 237 | } 238 | 239 | return [ 240 | 'first' => $name[0], 241 | 'last' => $name[1], 242 | ]; 243 | } 244 | } 245 | 246 | /** 247 | * Redacts ssn numbers and replaces them with a given string 248 | * 249 | * @param string $string 250 | * @param string $redacted // default '********' 251 | * @param bool $dashes // redact ssn with dashes 252 | * @param bool $noDashes // redact ssn without dashes 253 | * @return string 254 | */ 255 | public static function redactSsn($string, $redacted = '********', $dashes = true, $noDashes = true) 256 | { 257 | $ssnRegex = '/\b(?$methodName(...$args); 19 | }); 20 | } 21 | } 22 | } 23 | 24 | public function configurePackage(Package $package): void 25 | { 26 | /* 27 | * This class is a Package Service Provider 28 | * 29 | * More info: https://github.com/spatie/laravel-package-tools 30 | */ 31 | $package 32 | ->name('laravel-advanced-string') 33 | ->hasConfigFile('advanced-string'); 34 | } 35 | 36 | public function registeringPackage() 37 | { 38 | $this->publishes([ 39 | __DIR__.'/../config/advanced-string.php' => config_path('advanced-string.php'), 40 | ], 'laravel-advanced-string-config'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Facades/AdvStr.php: -------------------------------------------------------------------------------- 1 |