├── src ├── Exceptions │ └── UsernameGeneratorException.php ├── UsernameGeneratorServiceProvider.php ├── Contracts │ └── DriverContract.php ├── Drivers │ ├── Email.php │ └── Name.php └── Concerns │ └── HasUsername.php ├── .github ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ └── bug.yml └── workflows │ ├── fix-php-code-style-issues.yml │ ├── update-changelog.yml │ ├── dependabot-auto-merge.yml │ └── run-tests.yml ├── LICENSE.md ├── CHANGELOG.md ├── composer.json ├── CONTRIBUTING.md └── README.md /src/Exceptions/UsernameGeneratorException.php: -------------------------------------------------------------------------------- 1 | validate($name); 21 | 22 | if ($this->getTotalWords($name, $lastname) == 1) { 23 | return mb_strtolower($name, 'UTF-8'); 24 | } 25 | 26 | $lastname_array = $this->getLastnameAsArray($name, $lastname); 27 | $name_array = $this->getNameAsArray($name); 28 | 29 | $first_letter = $this->getFirstLetterName($name_array); 30 | $first_lastname = $this->getFirstLastname($lastname_array); 31 | $first_second_lastname = $this->getFirstLetterSecondLastname($lastname_array); 32 | 33 | return mb_strtolower($first_letter.$first_lastname.$first_second_lastname, 'UTF-8'); 34 | } 35 | 36 | /** 37 | * Get the first letter of the first name. 38 | */ 39 | protected function getFirstLetterName(array $firstname): string 40 | { 41 | return str_split($firstname[0])[0]; 42 | } 43 | 44 | /** 45 | * Get the first last name. 46 | */ 47 | protected function getFirstLastname(array $lastname): string 48 | { 49 | return count($lastname) > 0 ? $lastname[0] : ''; 50 | } 51 | 52 | /** 53 | * Get the first letter of the second last name. 54 | */ 55 | protected function getFirstLetterSecondLastname(array $lastname): string 56 | { 57 | return count($lastname) > 1 ? str_split($lastname[1])[0] : ''; 58 | } 59 | 60 | /** 61 | * Get the number of words that make up the name. 62 | */ 63 | protected function getTotalWords(string $name, ?string $lastname = null): int 64 | { 65 | return $lastname ? count(explode(' ', "{$name} {$lastname}")) : count(explode(' ', $name)); 66 | } 67 | 68 | /** 69 | * Validate that the initial conditions of the name driver. 70 | * 71 | * 72 | * @throws UsernameGeneratorException 73 | */ 74 | protected function validate(string $name): void 75 | { 76 | if (filter_var($name, FILTER_VALIDATE_EMAIL)) { 77 | throw new UsernameGeneratorException('Use the email driver, to generate a username from the email.'); 78 | } 79 | 80 | if ($name == null) { 81 | throw new UsernameGeneratorException('The name cannot be null'); 82 | } 83 | } 84 | 85 | /** 86 | * Get valid name as array. 87 | */ 88 | protected function getNameAsArray(string $name): array 89 | { 90 | $name_array = explode(' ', $name); 91 | $count_name = count($name_array); 92 | 93 | if ($count_name > 2) { 94 | $name_array = array_slice($name_array, 0, 2); 95 | } 96 | 97 | return $name_array; 98 | } 99 | 100 | /** 101 | * Get valid lastname as array. 102 | */ 103 | protected function getLastnameAsArray(string $name, ?string $lastname): array 104 | { 105 | $name_array = explode(' ', $name); 106 | $count_name = count($name_array); 107 | 108 | $lastname_array = $lastname ? explode(' ', $lastname) : []; 109 | 110 | if ($count_name > 2) { 111 | $lastname_array = array_slice($name_array, ((min($count_name, 4)) - 2), 2); 112 | } elseif (count($lastname_array) == 0) { 113 | $lastname_array = $count_name > 1 ? [$name_array[1]] : []; 114 | } 115 | 116 | return $lastname_array; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/Concerns/HasUsername.php: -------------------------------------------------------------------------------- 1 | setAttribute($model->getUsernameColumn(), $model->getUsername()); 31 | }); 32 | } 33 | 34 | /** 35 | * Driver to use to generate the username. 36 | */ 37 | protected function getUsernameDriver(): DriverContract 38 | { 39 | return new Name(); 40 | } 41 | 42 | /** 43 | * If the "Name" driver is used and the record stores the last name, separately, the value can be returned here. 44 | */ 45 | protected function getLastName(): ?string 46 | { 47 | return null; 48 | } 49 | 50 | /** 51 | * Generate the username for this model. 52 | * 53 | * @throws UsernameGeneratorException 54 | */ 55 | public function getUsername(): string 56 | { 57 | $driver = $this->getUsernameDriver(); 58 | 59 | $username = $driver->make($this->getName(), $this->getLastName()); 60 | $username = $this->transformUsername($username); 61 | 62 | return $this->setSuffixUsername($username); 63 | } 64 | 65 | /** 66 | * Apply transformation code to the username, by default it is transformed to lower case. 67 | */ 68 | protected function transformUsername(string $username): string 69 | { 70 | return mb_strtolower($username, 'UTF-8'); 71 | } 72 | 73 | /** 74 | * If the username is duplicate, a numeric suffix is set. 75 | */ 76 | protected function setSuffixUsername(string $username): string 77 | { 78 | $length = strlen($username); 79 | $usernames = $this->getDuplicateOrSimilarUsernames($username); 80 | 81 | if ($usernames->isEmpty()) { 82 | return $username; 83 | } 84 | 85 | $lasted = $usernames 86 | ->map(fn ($value) => intval(substr($value, $length))) 87 | ->sortDesc() 88 | ->first(); 89 | 90 | $suffix = 1; 91 | $suffix += $lasted; 92 | 93 | return "{$username}{$suffix}"; 94 | } 95 | 96 | /** 97 | * Configure the query and use soft delete, if present. 98 | */ 99 | protected function getUsernameQuery(): Builder 100 | { 101 | if (in_array(SoftDeletes::class, class_uses($this))) { 102 | return $this 103 | ->query() 104 | ->withTrashed(); 105 | } 106 | 107 | return $this->query(); 108 | } 109 | 110 | /** 111 | * Search similar or repeated username. 112 | */ 113 | protected function getDuplicateOrSimilarUsernames(string $username): Collection 114 | { 115 | return $this->getUsernameQuery() 116 | ->where($this->getUsernameColumn(), 'like', "{$username}%") 117 | ->where(fn (Builder $query) => $this->getUsernameRegexSimilarityQuery($query, $username)) 118 | ->pluck($this->getUsernameColumn()); 119 | } 120 | 121 | /** 122 | * Gets the similarity condition for the regex query. 123 | */ 124 | protected function getUsernameRegexSimilarityQuery(Builder $query, string $username): void 125 | { 126 | $column = $this->getUsernameColumn(); 127 | 128 | $driver = $this->getConnection()->getDriverName(); 129 | 130 | if ($driver === 'mysql') { 131 | $query 132 | ->where($column, 'like', $username) 133 | ->orWhere($column, 'regexp', "^{$username}[0-9]"); 134 | 135 | return; 136 | } 137 | 138 | if ($driver === 'pgsql') { 139 | $query 140 | ->where($column, 'like', $username) 141 | ->orWhere($column, '~', "^{$username}[0-9]"); 142 | 143 | return; 144 | } 145 | 146 | // Working on: sqlsrv 147 | $query 148 | ->where($column, 'like', $username) 149 | ->orWhere($column, 'like', "{$username}[0-9]%"); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Username Generator 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/luilliarcec/laravel-username-generator.svg)](https://packagist.org/packages/luilliarcec/laravel-username-generator) 4 | [![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/luilliarcec/laravel-username-generator/run-tests.yml?branch=master&label=tests&style=flat-square)](https://github.com/luilliarcec/laravel-username-generator/actions?query=workflow%3Arun-tests+branch%3Amaster) 5 | [![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/luilliarcec/laravel-username-generator/fix-php-code-style-issues.yml?branch=master&label=code%20style&style=flat-square)](https://github.com/luilliarcec/laravel-username-generator/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amaster) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/luilliarcec/laravel-username-generator)](https://packagist.org/packages/luilliarcec/laravel-username-generator) 7 | 8 | Buy Me A Coffee 9 | 10 | Laravel Username Generator is a package that allows the versatile generation of usernames, has a simple integration 11 | with Laravel. 12 | 13 | You can generate from the name of the user, taking into account that you do not use more than two names and two surnames 14 | in total. It can also be generated from the user's email. 15 | 16 | ## Installation 17 | 18 | You can install the package via composer: 19 | 20 | ### Version 5 was rewritten to be supported as a usable trait within your models. 21 | 22 | ### Please follow this guide if you are going to update to the new version. 23 | 24 | ```bash 25 | composer require luilliarcec/laravel-username-generator 26 | ``` 27 | 28 | ## Upgrade 29 | 30 | Upgrading to the new version is as easy as: 31 | 32 | - Update package 33 | - Delete the old configuration from your AppServiceProvider. 34 | 35 | ## Usage 36 | 37 | Add the Trait `Luilliarcec\LaravelUsernameGenerator\Concerns\HasUsername` to your Eloquent models in the 38 | use username. 39 | 40 | Remember that you must have a field in your table where you can store the `username`, preferably it is recommended 41 | make this field `unique`. 42 | 43 | ```php 44 | use Illuminate\Database\Eloquent\Model; 45 | use Luilliarcec\LaravelUsernameGenerator\Concerns\HasUsername; 46 | 47 | class User extends Model 48 | { 49 | use HasUsername; 50 | } 51 | ``` 52 | 53 | Within your model, you configure the username generator options. 54 | 55 | By default, you must configure the field where the username will be stored and searched: 56 | 57 | ```php 58 | protected function getUsernameColumn(): string 59 | { 60 | return 'my_username_column'; 61 | } 62 | ``` 63 | 64 | In addition to where the value will be extracted to generate the username: 65 | 66 | ```php 67 | protected function getName(): string 68 | { 69 | // This is the value, not the field name. 70 | return $this->name; 71 | } 72 | ``` 73 | 74 | **Remember the value of name cannot be empty `''`, this will throw an exception, just like using a driver 75 | incorrect for an incorrect value, for example using the `Name` driver to generate usernames from an email.** 76 | 77 | With this, your model will now be configured to work with usernames. 78 | 79 | ### Additional settings 80 | 81 | If your model stores the first and last name separately, you can set the `getLastName` function, so that it returns 82 | the value of your model's last name. 83 | 84 | ```php 85 | protected function getLastName(): ?string 86 | { 87 | // This is the value, not the field name. 88 | return $this->last_name; 89 | } 90 | ``` 91 | 92 | By default, the trait uses the driver `Luilliarcec\Laravel Username Generator\Drivers\Name`. This is modifiable 93 | overriding the `getUsernameDriver` method. 94 | 95 | ```php 96 | use Luilliarcec\LaravelUsernameGenerator\Drivers\Email; 97 | 98 | protected function getUsernameDriver(): DriverContract 99 | { 100 | return new Email(); 101 | } 102 | ``` 103 | 104 | By default, usernames are converted to lowercase. But if you want, convert them to uppercase or apply 105 | some logic or add a prefix or suffix, before the repeat search is processed, you can use the function 106 | `transformUsername`, this function receives the username as a parameter and returns a string. 107 | 108 | ```php 109 | protected function transformUsername(string $username): string 110 | { 111 | return mb_strtoupper($username, 'UTF-8'); 112 | } 113 | ``` 114 | 115 | #### Support for customs drivers 116 | 117 | You can create a class that implement the 118 | interface `Luilliarcec\LaravelUsernameGenerator\Contracts\DriverContract` 119 | and inside that class you can write all the logic to generate your username, remember to implement the make method that 120 | will be responsible for returning the username, for example: 121 | 122 | ```php 123 | namespace App\Support\Username\Drivers; 124 | 125 | use Luilliarcec\LaravelUsernameGenerator\Contracts\DriverContract; 126 | 127 | class CustomDriver implements DriverContract 128 | { 129 | public function make(string $name, string $lastname = null): string 130 | { 131 | // your code 132 | } 133 | } 134 | ``` 135 | 136 | ## ¡Important! 137 | 138 | Remember that like previous versions it is very important that you provide an Eloquent Model together with the column 139 | that stores the username. This is so that the package provides you with an alternate username if it is already in use. 140 | 141 | Skipping this step will cause an exception `UsernameGeneratorException` or that the generator does not work properly 142 | 143 | ## Examples 144 | 145 | Assume you have a user with the username `larcec` 146 | 147 | ```php 148 | $model = User::create(['name' => 'Luis Andrés Arce Cárdenas']); 149 | 150 | $model->username; // larcec 151 | ``` 152 | 153 | When using the package to generate the username, it will search thanks to Eloquent, in the database and will buy if that 154 | username already exists, if it exists, a suffix will be added to the username. 155 | 156 | The result would be as follows. 157 | 158 | ```php 159 | $model = User::create(['name' => 'Luciano Carlos Arce Cajamarca']); 160 | 161 | $model->username; // larcec1 162 | ``` 163 | 164 | Laravel Username Generator uses a convention for the creation of user names, takes the `first letter of the first name`, 165 | takes the `first last name`, and finally the `first letter of the second last name` 166 | 167 | However, Laravel Username Generator is so versatile that it can receive `only 1 name`, `1 name and 2 surnames`, and can 168 | even use the auxiliary surname parameter to pass the `two surnames separately`, in the following ways. 169 | 170 | ```php 171 | $model = User::create(['first_name' => 'Luis Andrés', 'last_name' => 'Arce Cárdenas']); 172 | $model = User::create(['name' => 'Luis Andrés Arce Cárdenas']); 173 | // This will generate the following username: larcec 174 | 175 | $model = User::create(['first_name' => 'Luis', 'last_name' => 'Arce']); 176 | $model = User::create(['name' => 'Luis Arce']); 177 | // This will generate the following username: larce 178 | 179 | $model = User::create(['first_name' => 'Luis', 'last_name' => 'Arce Cárdenas']); 180 | $model = User::create(['name' => 'Luis Arce Cárdenas']); 181 | // This will generate the following username: larcec 182 | ``` 183 | 184 | Keep these examples in mind, since passing a value of more or more than two names or two surnames without following the 185 | convention may cause an exception 186 | 187 | Finally, you can use the `email` driver, which will receive an email as the first and only parameter and take the user's 188 | email and use it as a username. 189 | 190 | ```php 191 | $model = User::create(['email' => 'luilliarcec@gmail.com']); 192 | // This will generate the following username: luilliarcec 193 | ``` 194 | 195 | ## Testing 196 | 197 | Can use docker-compose to run 198 | 199 | ``` bash 200 | docker-compose exec app composer test 201 | ``` 202 | 203 | ## Changelog 204 | 205 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 206 | 207 | ## Contributing 208 | 209 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 210 | 211 | ## Security 212 | 213 | If you discover any security related issues, please email luilliarcec@gmail.com instead of using the issue tracker. 214 | 215 | ## Credits 216 | 217 | - [Luis Andrés Arce C.](https://github.com/luilliarcec) 218 | - [All Contributors](../../contributors) 219 | 220 | ## License 221 | 222 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 223 | --------------------------------------------------------------------------------