├── .github ├── ISSUE_TEMPLATE │ ├── bug.yml │ └── config.yml ├── dependabot.yml └── workflows │ ├── dependabot-auto-merge.yml │ ├── fix-php-code-style-issues.yml │ ├── run-tests.yml │ └── update-changelog.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── composer.json └── src ├── Concerns └── HasUsername.php ├── Contracts └── DriverContract.php ├── Drivers ├── Email.php └── Name.php ├── Exceptions └── UsernameGeneratorException.php └── UsernameGeneratorServiceProvider.php /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Report an Issue or Bug with the Package 3 | title: "[Bug]: " 4 | labels: ["bug"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | We're sorry to hear you have a problem. Can you help us solve it by providing the following details. 10 | - type: textarea 11 | id: what-happened 12 | attributes: 13 | label: What happened? 14 | description: What did you expect to happen? 15 | placeholder: I cannot currently do X thing because when I do, it breaks X thing. 16 | validations: 17 | required: true 18 | - type: textarea 19 | id: how-to-reproduce 20 | attributes: 21 | label: How to reproduce the bug 22 | description: How did this occur, please add any config values used and provide a set of reliable steps if possible. 23 | placeholder: When I do X I see Y. 24 | validations: 25 | required: true 26 | - type: input 27 | id: package-version 28 | attributes: 29 | label: Package Version 30 | description: What version of our Package are you running? Please be as specific as possible 31 | placeholder: 2.0.0 32 | validations: 33 | required: true 34 | - type: input 35 | id: php-version 36 | attributes: 37 | label: PHP Version 38 | description: What version of PHP are you running? Please be as specific as possible 39 | placeholder: 8.2.0 40 | validations: 41 | required: true 42 | - type: input 43 | id: laravel-version 44 | attributes: 45 | label: Laravel Version 46 | description: What version of Laravel are you running? Please be as specific as possible 47 | placeholder: 9.0.0 48 | validations: 49 | required: true 50 | - type: dropdown 51 | id: operating-systems 52 | attributes: 53 | label: Which operating systems does with happen with? 54 | description: You may select more than one. 55 | multiple: true 56 | options: 57 | - macOS 58 | - Windows 59 | - Linux 60 | - type: textarea 61 | id: notes 62 | attributes: 63 | label: Notes 64 | description: Use this field to provide any other notes that you feel might be relevant to the issue. 65 | validations: 66 | required: false 67 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Ask a question 4 | url: https://github.com/luilliarcec/laravel-username-generator/discussions/new?category=q-a 5 | about: Ask the community for help 6 | - name: Request a feature 7 | url: https://github.com/luilliarcec/laravel-username-generator/discussions/new?category=ideas 8 | about: Share ideas for new features 9 | - name: Report a security issue 10 | url: https://github.com/luilliarcec/laravel-username-generator/security/policy 11 | about: Learn how to notify us for sensitive bugs 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | labels: 12 | - "dependencies" -------------------------------------------------------------------------------- /.github/workflows/dependabot-auto-merge.yml: -------------------------------------------------------------------------------- 1 | name: dependabot-auto-merge 2 | on: pull_request_target 3 | 4 | permissions: 5 | pull-requests: write 6 | contents: write 7 | 8 | jobs: 9 | dependabot: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.actor == 'dependabot[bot]' }} 12 | steps: 13 | 14 | - name: Dependabot metadata 15 | id: metadata 16 | uses: dependabot/fetch-metadata@v2.3.0 17 | with: 18 | github-token: "${{ secrets.GITHUB_TOKEN }}" 19 | 20 | - name: Auto-merge Dependabot PRs for semver-minor updates 21 | if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor'}} 22 | run: gh pr merge --auto --merge "$PR_URL" 23 | env: 24 | PR_URL: ${{github.event.pull_request.html_url}} 25 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 26 | 27 | - name: Auto-merge Dependabot PRs for semver-patch updates 28 | if: ${{steps.metadata.outputs.update-type == 'version-update:semver-patch'}} 29 | run: gh pr merge --auto --merge "$PR_URL" 30 | env: 31 | PR_URL: ${{github.event.pull_request.html_url}} 32 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 33 | -------------------------------------------------------------------------------- /.github/workflows/fix-php-code-style-issues.yml: -------------------------------------------------------------------------------- 1 | name: Fix PHP code style issues 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**.php' 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | php-code-styling: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v4 18 | with: 19 | ref: ${{ github.head_ref }} 20 | 21 | - name: Fix PHP code style issues 22 | uses: aglipanci/laravel-pint-action@2.5 23 | 24 | - name: Commit changes 25 | uses: stefanzweifel/git-auto-commit-action@v5 26 | with: 27 | commit_message: Fix styling 28 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: run-tests 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | test: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | fail-fast: true 14 | matrix: 15 | os: [ ubuntu-latest ] 16 | php: [ 8.2, 8.3 ] 17 | laravel: [ 10.*, 11.* ] 18 | stability: [ prefer-stable ] 19 | include: 20 | - laravel: 10.* 21 | testbench: 8.* 22 | carbon: ^2.63 23 | - laravel: 11.* 24 | testbench: 9.* 25 | carbon: ^2.63 26 | 27 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} 28 | 29 | services: 30 | mysql: 31 | image: mysql:8.0 32 | env: 33 | MYSQL_USER: laravel 34 | MYSQL_PASSWORD: laravel 35 | MYSQL_DATABASE: testing 36 | MYSQL_ROOT_PASSWORD: root 37 | ports: 38 | - 3306 39 | options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 40 | 41 | steps: 42 | - name: Checkout code 43 | uses: actions/checkout@v4 44 | 45 | - name: Setup PHP 46 | uses: shivammathur/setup-php@v2 47 | with: 48 | php-version: ${{ matrix.php }} 49 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo 50 | coverage: none 51 | 52 | - name: Setup problem matchers 53 | run: | 54 | echo "::add-matcher::${{ runner.tool_cache }}/php.json" 55 | echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" 56 | 57 | - name: Install dependencies 58 | run: | 59 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "nesbot/carbon:${{ matrix.carbon }}" --no-interaction --no-update 60 | composer update --${{ matrix.stability }} --prefer-dist --no-interaction 61 | 62 | - name: Execute tests 63 | run: vendor/bin/pest --ci 64 | env: 65 | DB_HOST: 127.0.0.1 66 | DB_PORT: ${{ job.services.mysql.ports[3306] }} 67 | DB_USERNAME: laravel 68 | DB_PASSWORD: laravel 69 | -------------------------------------------------------------------------------- /.github/workflows/update-changelog.yml: -------------------------------------------------------------------------------- 1 | name: "Update Changelog" 2 | 3 | on: 4 | release: 5 | types: [released] 6 | 7 | permissions: 8 | contents: write 9 | 10 | jobs: 11 | update: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v4 17 | with: 18 | ref: main 19 | 20 | - name: Update Changelog 21 | uses: stefanzweifel/changelog-updater-action@v1 22 | with: 23 | latest-version: ${{ github.event.release.name }} 24 | release-notes: ${{ github.event.release.body }} 25 | 26 | - name: Commit updated CHANGELOG 27 | uses: stefanzweifel/git-auto-commit-action@v5 28 | with: 29 | branch: main 30 | commit_message: Update CHANGELOG 31 | file_pattern: CHANGELOG.md 32 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `laravel-username-generator` will be documented in this file 4 | 5 | ## 3.0.0 - 2022-04-18 6 | 7 | - Support only for the latest version of Laravel 8 | 9 | ## 2.2.0 - 2021-07-19 10 | 11 | - Default values were configured to the generator properties 12 | 13 | ## 2.1.2 - 2021-07-19 14 | 15 | - String type removed from setDriver function on facade 16 | 17 | ## 2.1.1 - 2021-04-19 18 | 19 | - Fixed bug that generated random numbers in the username 20 | 21 | ## 2.1.0 - 2021-04-18 22 | 23 | - Add withoutTrashed method 24 | - Remove parameter boolean in withTrashed method 25 | 26 | ## 2.0.0 - 2021-01-30 27 | 28 | - Performance improvement 29 | - Support for softdelete 30 | - Support for customs drivers 31 | - Support for multiple models 32 | - Configuration from functions 33 | 34 | ## 1.2.0 - 2020-09-13 35 | 36 | - Test restructuring 37 | - Support php 8.0 38 | - Rename config file 39 | 40 | ## 1.1.1 - 2020-09-13 41 | 42 | - Laravel 8 compatibility 43 | 44 | ## 1.1.0 - 2020-04-15 45 | 46 | - Code optimization 47 | 48 | ## 1.0.3 - 2020-03-04 49 | 50 | - Refactor code quality 51 | - Laravel 7 compatibility 52 | 53 | ## 1.0.2 - 2020-03-02 54 | 55 | - Refactor rename facade 56 | 57 | ## 1.0.1 - 2020-01-27 58 | 59 | - Change php version required 60 | 61 | ## 1.0.0 - 2020-01-26 62 | 63 | - Generate user name by name 64 | - Generate user name by email 65 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | Please read and understand the contribution guide before creating an issue or pull request. 6 | 7 | ## Etiquette 8 | 9 | This project is open source, and as such, the maintainers give their free time to build and maintain the source code 10 | held within. They make the code freely available in the hope that it will be of use to other developers. It would be 11 | extremely unfair for them to suffer abuse or anger for their hard work. 12 | 13 | Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the 14 | world that developers are civilized and selfless people. 15 | 16 | It's the duty of the maintainer to ensure that all submissions to the project are of sufficient 17 | quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used. 18 | 19 | ## Viability 20 | 21 | When requesting or submitting new features, first consider whether it might be useful to others. Open 22 | source projects are used by many developers, who may have entirely different needs to your own. Think about 23 | whether or not your feature is likely to be used by other users of the project. 24 | 25 | ## Procedure 26 | 27 | Before filing an issue: 28 | 29 | - Attempt to replicate the problem, to ensure that it wasn't a coincidental incident. 30 | - Check to make sure your feature suggestion isn't already present within the project. 31 | - Check the pull requests tab to ensure that the bug doesn't have a fix in progress. 32 | - Check the pull requests tab to ensure that the feature isn't already in progress. 33 | 34 | Before submitting a pull request: 35 | 36 | - Check the codebase to ensure that your feature doesn't already exist. 37 | - Check the pull requests to ensure that another person hasn't already submitted the feature or fix. 38 | 39 | ## Requirements 40 | 41 | If the project maintainer has any additional requirements, you will find them listed here. 42 | 43 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](https://pear.php.net/package/PHP_CodeSniffer). 44 | 45 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 46 | 47 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 48 | 49 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option. 50 | 51 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 52 | 53 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](https://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. 54 | 55 | **Happy coding**! 56 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Luis Andrés Arce C. 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 all 13 | 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 THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "luilliarcec/laravel-username-generator", 3 | "description": "Laravel Username Generator is a package that allows the versatile generation of user names, has a simple integration with Laravel.", 4 | "keywords": [ 5 | "luilliarcec", 6 | "laravel-username-generator", 7 | "laravel", 8 | "username", 9 | "generator" 10 | ], 11 | "homepage": "https://github.com/luilliarcec/laravel-username-generator", 12 | "license": "MIT", 13 | "type": "library", 14 | "authors": [ 15 | { 16 | "name": "Luis Andrés Arce C.", 17 | "email": "luilliarcec@gmail.com", 18 | "role": "Developer" 19 | } 20 | ], 21 | "require": { 22 | "php": "^8.2|^8.3", 23 | "laravel/framework": "^10.0|^11.0", 24 | "spatie/laravel-package-tools": "^1.14.0" 25 | }, 26 | "require-dev": { 27 | "laravel/pint": "^1.0", 28 | "orchestra/testbench": "^8.0|^9.0", 29 | "pestphp/pest": "^2.0", 30 | "pestphp/pest-plugin-laravel": "^2.0" 31 | }, 32 | "autoload": { 33 | "psr-4": { 34 | "Luilliarcec\\LaravelUsernameGenerator\\": "src" 35 | } 36 | }, 37 | "autoload-dev": { 38 | "psr-4": { 39 | "Tests\\": "tests" 40 | } 41 | }, 42 | "scripts": { 43 | "test": "vendor/bin/pest", 44 | "test-coverage": "vendor/bin/pest --coverage", 45 | "format": "vendor/bin/pint" 46 | }, 47 | "config": { 48 | "sort-packages": true, 49 | "allow-plugins": { 50 | "pestphp/pest-plugin": true 51 | } 52 | }, 53 | "extra": { 54 | "laravel": { 55 | "providers": [ 56 | "Luilliarcec\\LaravelUsernameGenerator\\UsernameGeneratorServiceProvider" 57 | ] 58 | } 59 | }, 60 | "minimum-stability": "dev", 61 | "prefer-stable": true 62 | } 63 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/Contracts/DriverContract.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/Exceptions/UsernameGeneratorException.php: -------------------------------------------------------------------------------- 1 |