├── source ├── database.legecy │ ├── .gitignore │ ├── seeds │ │ └── EarMark.php │ ├── factories │ │ └── EarMarkFactory.php │ └── migrations │ │ ├── 2019_05_27_062621_create_accruals_table.php │ │ ├── 2019_05_22_090330_create_earmark_table.php │ │ └── 2019_05_22_090330_create_earmark_hold_table.php ├── database │ ├── seeders │ │ └── EarMark.php │ ├── factories │ │ └── EarMarkFactory.php │ └── migrations │ │ ├── 2024_08_02_000000_create_accruals_table.php │ │ ├── 2024_08_02_000000_create_earmark_table.php │ │ └── 2024_08_02_000000_create_earmark_hold_table.php ├── Facades │ └── EarMarkFacade.php ├── Helpers │ └── Boost.php ├── Models │ ├── Hold.php │ ├── EarMark.php │ └── Accrual.php ├── config │ ├── default.php │ └── earmark.php ├── Providers │ └── EventServiceProvider.php ├── Listeners │ └── EarMarkLowHold.php ├── Commands │ └── EarMarkInstall.php ├── Events │ └── EarMarkRefill.php ├── Jobs │ └── EarmarkQueue.php ├── EarmarkServiceProvider.php └── Http │ └── Controllers │ └── Serial.php ├── test ├── Baseline │ ├── database │ │ ├── .gitignore │ │ ├── factories │ │ │ ├── BaseLineAlphaFactory.php │ │ │ └── UserFactory.php │ │ ├── seeds │ │ │ ├── BaseLineAlpha.php │ │ │ └── DatabaseSeeder.php │ │ └── migrations │ │ │ ├── 2020_04_18_123058_create_base_line_alphas_table.php │ │ │ ├── 2014_10_12_100000_create_password_resets_table.php │ │ │ └── 2014_10_12_000000_create_users_table.php │ ├── MyTrait.php │ ├── Models │ │ └── BaseLineAlpha.php │ ├── EloquentTest.php │ ├── BaselineTest.php │ └── UnitAbstract.php └── Models │ ├── AccrualTest.php │ ├── HoldTest.php │ ├── EarMarkTest.php │ ├── database │ └── migrations │ │ ├── 2019_05_27_062621_create_accruals_table.php │ │ ├── 2019_05_22_090330_create_earmark_table.php │ │ └── 2019_05_22_090330_create_earmark_hold_table.php │ └── AbstractTestCase.php ├── .styleci.yml ├── .gitignore ├── .coveralls.yml ├── phpunit.xml ├── LICENSE ├── composer.json └── README.md /source/database.legecy/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite 2 | -------------------------------------------------------------------------------- /test/Baseline/database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite 2 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: laravel 2 | 3 | risky: false 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /build 3 | composer.lock 4 | *.cache 5 | .DS_Store 6 | *.bak -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | # for php-coveralls 2 | service_name: travis-ci 3 | coverage_clover: build/logs/clover*.xml -------------------------------------------------------------------------------- /test/Baseline/MyTrait.php: -------------------------------------------------------------------------------- 1 | define(BaseLineAlpha::class, function (Faker $faker) { 7 | return [ 8 | 'value' => $faker->sentence(6), 9 | ]; 10 | }); 11 | -------------------------------------------------------------------------------- /source/database/seeders/EarMark.php: -------------------------------------------------------------------------------- 1 | create(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/Baseline/database/seeds/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | call([ 15 | BaseLineAlpha::class, 16 | ]); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /source/Facades/EarMarkFacade.php: -------------------------------------------------------------------------------- 1 | create(); 12 | $this->assertEquals(BaseLineAlpha::count(), 50); 13 | echo '|'; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /source/Helpers/Boost.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 14 | } 15 | 16 | public function test_trait_test() 17 | { 18 | $this->assertTrue($this->stub()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /source/Models/Hold.php: -------------------------------------------------------------------------------- 1 | save(); 14 | $this->assertTrue(Accrual::count() == 1); 15 | } 16 | 17 | #[CoversMethod('probe')] 18 | public function testAccrualClass() 19 | { 20 | $this->assertTrue(Accrual::probe()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/Models/HoldTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(Hold::probe()); 14 | } 15 | 16 | public function test_commandTest() 17 | { 18 | $this->artisan('earmark:config') 19 | ->expectsOutput('EarMark installed successfully.') 20 | ->assertExitCode(0); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /source/config/default.php: -------------------------------------------------------------------------------- 1 | Poing\Earmark\Models\EarMark::class, 14 | 15 | 'columns' => [ 16 | 'digit' => 'digit', 17 | 'group' => 'prefix', 18 | ], 19 | 20 | 'hold_model' => Poing\Earmark\Models\Hold::class, 21 | 'accrual' => Poing\Earmark\Models\Accrual::class, 22 | 23 | ]; 24 | -------------------------------------------------------------------------------- /source/database.legecy/factories/EarMarkFactory.php: -------------------------------------------------------------------------------- 1 | autoIncrement(); 8 | 9 | $factory->define(Poing\Earmark\Models\EarMark::class, function (Faker $faker) use ($count) { 10 | $count->next(); 11 | $count->next(); 12 | 13 | return [ 14 | /* 15 | 'digit' => $faker->numberBetween( 16 | config('earmark.range.min'), 17 | config('earmark.range.max') 18 | ), 19 | */ 20 | 'digit' => $autoIncrement->current(), 21 | 'prefix' => config('earmark.prefix'), 22 | 'type' => $faker->word, 23 | ]; 24 | }); 25 | -------------------------------------------------------------------------------- /source/database/factories/EarMarkFactory.php: -------------------------------------------------------------------------------- 1 | autoIncrement(); 17 | 18 | $count->next(); 19 | $count->next(); 20 | 21 | return [ 22 | 'digit' => $count->current(), 23 | 'prefix' => config('earmark.prefix'), 24 | 'type' => $this->faker->word, 25 | ]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /source/database/migrations/2024_08_02_000000_create_accruals_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->timestamps(); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | * 25 | * @return void 26 | */ 27 | public function down() 28 | { 29 | Schema::dropIfExists('accruals'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/Models/EarMarkTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(EarMark::probe()); 14 | } 15 | 16 | public function testRefill() 17 | { 18 | $earmark = new \Poing\Earmark\Http\Controllers\Serial; 19 | $data = $earmark->get(); 20 | $earmark->get(30); 21 | $earmark->unset($data); 22 | $data = $earmark->get(30); 23 | $earmark->get(30); 24 | $earmark->unset($data); 25 | $earmark->increment(); 26 | $this->assertEquals(30, count($earmark->get(30))); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/Models/database/migrations/2019_05_27_062621_create_accruals_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->timestamps(); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | * 25 | * @return void 26 | */ 27 | public function down() 28 | { 29 | Schema::dropIfExists('earmark_accrual'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /source/database.legecy/migrations/2019_05_27_062621_create_accruals_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->timestamps(); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | * 25 | * @return void 26 | */ 27 | public function down() 28 | { 29 | Schema::dropIfExists('earmark_accrual'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /source/Providers/EventServiceProvider.php: -------------------------------------------------------------------------------- 1 | [ 17 | \Poing\Earmark\Listeners\EarMarkLowHold::class, 18 | ], 19 | ]; 20 | 21 | /** 22 | * Register any events for your application. 23 | * 24 | * @return void 25 | */ 26 | public function boot() 27 | { 28 | parent::boot(); 29 | 30 | // 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/Baseline/database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | define(App\User::class, function (Faker $faker) { 17 | static $password; 18 | 19 | return [ 20 | 'name' => $faker->name, 21 | 'email' => $faker->unique()->safeEmail, 22 | 'password' => $password ?: $password = bcrypt('secret'), 23 | 'remember_token' => str_random(10), 24 | ]; 25 | }); 26 | -------------------------------------------------------------------------------- /test/Baseline/database/migrations/2020_04_18_123058_create_base_line_alphas_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('value'); 19 | $table->timestamps(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::dropIfExists('base_line_alphas'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/Baseline/database/migrations/2014_10_12_100000_create_password_resets_table.php: -------------------------------------------------------------------------------- 1 | string('email')->index(); 18 | $table->string('token'); 19 | $table->timestamp('created_at')->nullable(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::dropIfExists('password_resets'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /source/Listeners/EarMarkLowHold.php: -------------------------------------------------------------------------------- 1 | refill(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/Baseline/database/migrations/2014_10_12_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('name'); 19 | $table->string('email')->unique(); 20 | $table->string('password'); 21 | $table->rememberToken(); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function down() 32 | { 33 | Schema::dropIfExists('users'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | ./test 12 | 13 | 14 | 15 | 16 | 17 | 18 | ./source 19 | 20 | 21 | ./source/database 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /source/Commands/EarMarkInstall.php: -------------------------------------------------------------------------------- 1 | comment('Publishing EarMark Configuration...'); 41 | $this->callSilent('vendor:publish', ['--tag' => 'earmark-config']); 42 | $this->info('EarMark installed successfully.'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /source/Events/EarMarkRefill.php: -------------------------------------------------------------------------------- 1 | prefix = $prefix; 27 | } 28 | 29 | /** 30 | * Get the channels the event should broadcast on. 31 | * 32 | * @codeCoverageIgnore 33 | * 34 | * @return \Illuminate\Broadcasting\Channel|array 35 | */ 36 | public function broadcastOn() 37 | { 38 | //Log::info('Event EarMarkRefill Broadcast.'); 39 | 40 | return new PrivateChannel('channel-name'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Brian LaVallee 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 | -------------------------------------------------------------------------------- /source/database/migrations/2024_08_02_000000_create_earmark_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->integer(config('earmark.columns.digit')); 19 | $table->string(config('earmark.columns.group'))->nullable(); 20 | $table->timestamps(); 21 | 22 | $table->index(config('earmark.columns.digit')); 23 | $table->index(config('earmark.columns.group')); 24 | }); 25 | 26 | // Poing\Earmark\Models\EarMark::max('digit') 27 | // Poing\Earmark\Models\EarMark::where('type','alpha')->max('digit') 28 | } 29 | 30 | /** 31 | * Reverse the migrations. 32 | * 33 | * @return void 34 | */ 35 | public function down() 36 | { 37 | Schema::dropIfExists('earmark'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /source/database.legecy/migrations/2019_05_22_090330_create_earmark_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->integer(config('earmark.columns.digit')); 19 | $table->string(config('earmark.columns.group'))->nullable(); 20 | $table->timestamps(); 21 | 22 | $table->index(config('earmark.columns.digit')); 23 | $table->index(config('earmark.columns.group')); 24 | }); 25 | 26 | // Poing\Earmark\Models\EarMark::max('digit') 27 | // Poing\Earmark\Models\EarMark::where('type','alpha')->max('digit') 28 | } 29 | 30 | /** 31 | * Reverse the migrations. 32 | * 33 | * @return void 34 | */ 35 | public function down() 36 | { 37 | Schema::dropIfExists('earmark'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/Models/database/migrations/2019_05_22_090330_create_earmark_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->integer(config('earmark.columns.digit')); 19 | $table->string(config('earmark.columns.group'))->nullable(); 20 | $table->timestamps(); 21 | 22 | $table->index(config('earmark.columns.digit')); 23 | $table->index(config('earmark.columns.group')); 24 | }); 25 | 26 | // Poing\Earmark\Models\EarMark::max('digit') 27 | // Poing\Earmark\Models\EarMark::where('type','alpha')->max('digit') 28 | } 29 | 30 | /** 31 | * Reverse the migrations. 32 | * 33 | * @return void 34 | */ 35 | public function down() 36 | { 37 | Schema::dropIfExists('earmark'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /source/database/migrations/2024_08_02_000000_create_earmark_hold_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->integer(config('earmark.columns.digit')); 19 | $table->string(config('earmark.columns.group'))->nullable(); 20 | $table->timestamps(); 21 | 22 | $table->index(config('earmark.columns.digit')); 23 | $table->index(config('earmark.columns.group')); 24 | }); 25 | 26 | // Poing\Earmark\Models\EarMark::max('digit') 27 | // Poing\Earmark\Models\EarMark::where('type','alpha')->max('digit') 28 | } 29 | 30 | /** 31 | * Reverse the migrations. 32 | * 33 | * @return void 34 | */ 35 | public function down() 36 | { 37 | Schema::dropIfExists('earmark_hold'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /source/database.legecy/migrations/2019_05_22_090330_create_earmark_hold_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->integer(config('earmark.columns.digit')); 19 | $table->string(config('earmark.columns.group'))->nullable(); 20 | $table->timestamps(); 21 | 22 | $table->index(config('earmark.columns.digit')); 23 | $table->index(config('earmark.columns.group')); 24 | }); 25 | 26 | // Poing\Earmark\Models\EarMark::max('digit') 27 | // Poing\Earmark\Models\EarMark::where('type','alpha')->max('digit') 28 | } 29 | 30 | /** 31 | * Reverse the migrations. 32 | * 33 | * @return void 34 | */ 35 | public function down() 36 | { 37 | Schema::dropIfExists('earmark_hold'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/Models/database/migrations/2019_05_22_090330_create_earmark_hold_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->integer(config('earmark.columns.digit')); 19 | $table->string(config('earmark.columns.group'))->nullable(); 20 | $table->timestamps(); 21 | 22 | $table->index(config('earmark.columns.digit')); 23 | $table->index(config('earmark.columns.group')); 24 | }); 25 | 26 | // Poing\Earmark\Models\EarMark::max('digit') 27 | // Poing\Earmark\Models\EarMark::where('type','alpha')->max('digit') 28 | } 29 | 30 | /** 31 | * Reverse the migrations. 32 | * 33 | * @return void 34 | */ 35 | public function down() 36 | { 37 | Schema::dropIfExists('earmark_hold'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /source/Jobs/EarmarkQueue.php: -------------------------------------------------------------------------------- 1 | prefix = $prefix; 47 | $this->suffix = $suffix; 48 | $this->padding = $padding; 49 | $this->min = $min; 50 | $this->max = $max; 51 | } 52 | 53 | /** 54 | * Execute the job. 55 | * 56 | * @return void 57 | */ 58 | public function handle() 59 | { 60 | $earmark = new Serial($this->prefix, $this->suffix, $this->padding, $this->min, $this->max); 61 | $earmark->refill(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/Models/AbstractTestCase.php: -------------------------------------------------------------------------------- 1 | loadMigrationsFrom(__DIR__.'/database/migrations'); 16 | $this->artisan('migrate'); 17 | } 18 | 19 | /** 20 | * Define environment setup. 21 | * 22 | * @param \Illuminate\Foundation\Application $app 23 | * @return void 24 | */ 25 | protected function getEnvironmentSetUp($app) 26 | { 27 | // Setup default database to use sqlite :memory: 28 | // Make sure php-sqlite3 is installed on the system. 29 | $app['config']->set('database.default', 'testbench'); 30 | $app['config']->set('database.connections.testbench', [ 31 | 'driver' => 'sqlite', 32 | 'database' => ':memory:', 33 | 'prefix' => '', 34 | ]); 35 | } 36 | 37 | /** 38 | * Get package providers. At a minimum this is the package 39 | * being tested, but also would include packages upon which 40 | * our package depends, e.g. Cartalyst/Sentry 41 | * In a normal app environment these would be added to the 42 | * 'providers' array in the config/app.php file. 43 | * 44 | * @param \Illuminate\Foundation\Application $app 45 | * @return array 46 | */ 47 | protected function getPackageProviders($app) 48 | { 49 | return [ 50 | \Poing\Earmark\EarmarkServiceProvider::class, 51 | //\Poing\Ylem\YlemServiceProvider::class, 52 | //'Cartalyst\Sentry\SentryServiceProvider', 53 | //'YourProject\YourPackage\YourPackageServiceProvider', 54 | ]; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test/Baseline/UnitAbstract.php: -------------------------------------------------------------------------------- 1 | loadMigrationsFrom(__DIR__.'/database/migrations'); 16 | $this->withFactories(__DIR__.'/database/factories'); 17 | $this->artisan('migrate'); 18 | } 19 | 20 | /** 21 | * Define environment setup. 22 | * 23 | * @param \Illuminate\Foundation\Application $app 24 | * @return void 25 | */ 26 | protected function getEnvironmentSetUp($app) 27 | { 28 | // Setup default database to use sqlite :memory: 29 | // Make sure php-sqlite3 is installed on the system. 30 | $app['config']->set('database.default', 'testbench'); 31 | $app['config']->set('database.connections.testbench', [ 32 | 'driver' => 'sqlite', 33 | 'database' => ':memory:', 34 | 'prefix' => '', 35 | ]); 36 | } 37 | 38 | /** 39 | * Get package providers. At a minimum this is the package 40 | * being tested, but also would include packages upon which 41 | * our package depends, e.g. Cartalyst/Sentry 42 | * In a normal app environment these would be added to the 43 | * 'providers' array in the config/app.php file. 44 | * 45 | * @param \Illuminate\Foundation\Application $app 46 | * @return array 47 | */ 48 | protected function getPackageProviders($app) 49 | { 50 | return [ 51 | //\Poing\Earmark\EarmarkServiceProvider::class, 52 | //\Poing\Ylem\YlemServiceProvider::class, 53 | //'Cartalyst\Sentry\SentryServiceProvider', 54 | //'YourProject\YourPackage\YourPackageServiceProvider', 55 | ]; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "poing/earmark", 3 | "description": "Laravel package to generate values in a unique and customizable series.", 4 | "keywords": [ 5 | "auto-increment", 6 | "autoincrement", 7 | "consecutive", 8 | "continual", 9 | "continued", 10 | "continuing", 11 | "eloquent", 12 | "ensuing", 13 | "extension number", 14 | "extension", 15 | "following", 16 | "going on", 17 | "increment", 18 | "laravel", 19 | "prefix", 20 | "sequent", 21 | "sequential values", 22 | "sequential", 23 | "sequential", 24 | "serial number", 25 | "serial", 26 | "series", 27 | "succedent", 28 | "succeeding", 29 | "successional", 30 | "successive", 31 | "suffix", 32 | "unique", 33 | "values", 34 | "zero fill", 35 | "zero-fill", 36 | "zerofill" 37 | ], 38 | "version": "0.2.2", 39 | "homepage": "https://github.com/poing/earmark", 40 | "license": "MIT", 41 | "authors": [ 42 | { 43 | "name": "Brian LaVallee", 44 | "email": "brian.lavallee@invite-comm.jp" 45 | } 46 | ], 47 | "support": { 48 | "issues": "https://github.com/poing/earmark/issues", 49 | "irc": "irc://irc.freenode.org/poing" 50 | }, 51 | "require": { 52 | "php": ">=8.1", 53 | "illuminate/console": ">=11.0", 54 | "illuminate/database": ">=11.0", 55 | "illuminate/events": ">=11.0", 56 | "illuminate/filesystem": ">=11.0", 57 | "illuminate/support": ">=11.0" 58 | }, 59 | "require-dev": { 60 | "orchestra/testbench": "^9.0", 61 | "phpunit/phpunit": "^11.0", 62 | "php-coveralls/php-coveralls": "^2.2" 63 | }, 64 | "autoload": { 65 | "psr-4": { 66 | "Poing\\Earmark\\": "source/" 67 | } 68 | }, 69 | "autoload-dev": { 70 | "psr-4": { 71 | "Earmark\\Test\\": "test/" 72 | } 73 | }, 74 | "minimum-stability": "stable", 75 | "extra": { 76 | "laravel": { 77 | "providers": [ 78 | "Poing\\Earmark\\EarmarkServiceProvider" 79 | ] 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /source/config/earmark.php: -------------------------------------------------------------------------------- 1 | 100, 18 | 19 | /* 20 | |-------------------------------------------------------------------------- 21 | | Number Ranges 22 | |-------------------------------------------------------------------------- 23 | | This determines the starting number of the sequence. The 'max' value 24 | | may be used in the future. 25 | | 26 | */ 27 | 28 | 'range' => [ 29 | 30 | 'min' => 2000, 31 | 'max' => 9999, // reserved for future use 32 | 33 | ], 34 | 35 | /* 36 | |-------------------------------------------------------------------------- 37 | | Zero Padding 38 | |-------------------------------------------------------------------------- 39 | | The number of zeros to prefix the digits with. To provide uniformity. 40 | | 41 | */ 42 | 43 | 'padding' => 0, 44 | 45 | /* 46 | |-------------------------------------------------------------------------- 47 | | Default Prefix & Suffix 48 | |-------------------------------------------------------------------------- 49 | | The default string to affix to the befining of the digits. 50 | | 51 | */ 52 | 53 | 'prefix' => null, 54 | 'suffix' => null, // reserved for future use 55 | 56 | /* 57 | |-------------------------------------------------------------------------- 58 | | Custom Model 59 | |-------------------------------------------------------------------------- 60 | | Use your own model and column vaules. Use at your own risk. 61 | | 62 | */ 63 | 64 | /* 65 | 'model' => Poing\Earmark\Models\EarMark::class, 66 | 67 | 'columns' => [ 68 | 'digit' => 'digit', // The interger 69 | 'group' => 'group', // The prefix string 70 | ], 71 | */ 72 | 73 | ]; 74 | -------------------------------------------------------------------------------- /source/EarmarkServiceProvider.php: -------------------------------------------------------------------------------- 1 | loadMigrationsFrom(__DIR__.'/database/migrations'); 37 | 38 | // Publish Configuration Files 39 | $this->publishes([ 40 | __DIR__.'/config/earmark.php' => config_path('earmark.php'), 41 | ], 'earmark-config'); 42 | 43 | // Load Commands 44 | if ($this->app->runningInConsole()) { 45 | $this->commands([ 46 | EarMarkInstall::class, 47 | ]); 48 | } 49 | 50 | $loader = AliasLoader::getInstance(); 51 | $loader->alias('Earmarked', Facades\EarMarkFacade::class); 52 | $loader->alias('Earmark', Serial::class); 53 | } 54 | 55 | /** 56 | * Register the service provider. 57 | * 58 | * @return void 59 | */ 60 | public function register() 61 | { 62 | // Register Factories 63 | $this->registerEloquentFactoriesFrom(__DIR__.'/database/factories'); 64 | 65 | // Default Package Configuration 66 | $this->mergeConfigFrom(__DIR__.'/config/earmark.php', 'earmark'); 67 | $this->mergeConfigFrom(__DIR__.'/config/default.php', 'earmark'); 68 | 69 | $this->app->register(EventServiceProvider::class); 70 | 71 | // @codeCoverageIgnoreStart 72 | //Poing\Earmark\Http\Controllers 73 | $this->app->singleton('earmark', function () { 74 | return new Serial; 75 | }); 76 | 77 | //Poing\Earmark\Http\Controllers 78 | $this->app->singleton('sequence', function () { 79 | return new Sequential; 80 | }); 81 | // @codeCoverageIgnoreEnd 82 | } 83 | 84 | /** 85 | * Register factories. 86 | * 87 | * @param string $path 88 | * @return void 89 | */ 90 | protected function registerEloquentFactoriesFrom($path) 91 | { 92 | if (app()->runningInConsole()) { 93 | $this->loadFactoriesFrom($path); 94 | } 95 | } 96 | 97 | /* Depreciated 98 | protected function registerEloquentFactoriesFrom($path) 99 | { 100 | $this->app->make(EloquentFactory::class)->load($path); 101 | } 102 | */ 103 | } 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![StyleCI](https://github.styleci.io/repos/190128345/shield?branch=0.1.8&style=flat)](https://github.styleci.io/repos/190128345) 2 | [![Coverage Status](https://coveralls.io/repos/github/poing/earmark/badge.svg?branch=0.1.8)](https://coveralls.io/github/poing/earmark?branch=master) 3 | 4 | # Earmark 5 | 6 | A Laravel package to earmark sequential values in a series and eliminate any gaps in the series when values are `unset`. *Allowing for values to be reused.* 7 | 8 | It can be used to fetch the next value (or array of values) to be used in an application. Database locking is used to *prevent* duplicate values from being returned. 9 | 10 | # An example... 11 | 12 | Reserving the next available phone extension for a user. The phone extension can be reserved by the active session, preventing other sessions from obtaining the same value. 13 | 14 | When a user leaves the phone extension can be *recycled*, the value can be `unset()`. Making the value available again for a *future* request. 15 | 16 | # Getting Started 17 | 18 | ## 1. Install EarMark 19 | 20 | Run this at the command line: 21 | 22 | ``` 23 | composer require poing/earmark 24 | ``` 25 | 26 | This will update composer.json and install the package into the vendor/ directory. 27 | 28 | ## 2. Publish the EarMark Configuration File 29 | 30 | To over-ride the default settings, initialize the config file by running this command: 31 | 32 | ``` 33 | php artisan earmark:config 34 | ``` 35 | 36 | Then open config/earmark.php and edit the settings to meet the requirements of your application. 37 | 38 | ## 3. Run the Migrations 39 | 40 | Run the package migration files at the command line: 41 | 42 | ``` 43 | php artisan migrate 44 | ``` 45 | 46 | ## 4. Recommended 47 | 48 | This package is designed to use Laravel Queues, to defer the time consuming task of repopulating the available pool of values. 49 | 50 | You may want to change the default `QUEUE_CONNECTION` to use another strategy. But the package *also* works with the default `sync`, but **may** cause latency in your application. 51 | 52 | # Settings 53 | 54 | ## Hold Size 55 | 56 | This is the number of series values that are kept in an available pool. Once the pool drops below a certain level, more values are added to the pool. *This is where unused values are recycled.* 57 | 58 | ### Important: 59 | 60 | * Replenishing the hold **will** be time consuming and *may* take **minutes** to complete. 61 | * Depending on how you use Earmark, the hold size should be a *multiple* of the **maximum** `get()` requests expected. 62 | * Hold size *should* be configured to allow for a sufficient number of `get()` requests **after** falling below one-third. 63 | 64 | ## Number Range 65 | 66 | This package *currently* supports a minimal value. `range.min` will define the starting value of the series. *Default: 2000* 67 | 68 | ``` 69 | 2000 70 | 2001 71 | 2002 72 | 2003 73 | ``` 74 | 75 | *`range.max` may be used in the future.* 76 | 77 | ## Zero Padding 78 | 79 | Allows the output value to be zero-padded, depending on the needs of the application. 80 | 81 | ``` 82 | 2004 83 | 002005 84 | 00000000002006 85 | ``` 86 | 87 | ## Prefix & Suffix 88 | 89 | Appends a *prefix* to the output value. 90 | 91 | ``` 92 | ALPHA2007 93 | ALPHA002008 94 | ALPHA00000000002009 95 | ``` 96 | 97 | *`suffix` may be used in the future.* 98 | 99 | 100 | # How to Use 101 | 102 | There are two ways to use this package: 103 | 104 | * `Earmarked` to use the *default* values in the pool. *The default values are already "Earmarked".* 105 | * `Earmark` to create a **new** pool of values to use. *Use custom settings with a new "Earmark".* 106 | 107 | Available Functions: 108 | 109 | * `get()` 110 | * Returns a *single* formated value. 111 | * Will accept *option* integer to return an array of formatted values. 112 | * `unset($value)` 113 | * Accepts *formated* values as a string or array. 114 | 115 | ## Simple Usage 116 | 117 | With the values from the configuration file, use `Earmarked::get()` and `Earmarked::unset()`. 118 | 119 | ```php 120 | $serial = Earmarked::get(); // Returns: '2010' 121 | Earmarked::unset($serial); 122 | ``` 123 | 124 | You can also *specify* the number of values to return in an array: 125 | 126 | ```php 127 | $serial = Earmarked::get(3); // Returns: [ '2011', '2012', '2013', ] 128 | Earmarked::unset($serial); 129 | ``` 130 | 131 | ## Advanced Usage 132 | 133 | You can *initialize* a **new** series using `Earmark()` and supplying the following variables: 134 | 135 | * prefix 136 | * suffix *(non-functional placeholder)* 137 | * padding 138 | * min 139 | * max *(non-functional placeholder)* 140 | 141 | ```php 142 | // Earmark(prefix, suffix, padding, min, max) 143 | $earmark = new Earmark('ZULU', null, 10, 5000, null); 144 | $earmark->get(); // Returns: 'ZULU0000005000' 145 | $earmark->get(3); // Returns: [ 'ZULU0000005001', 'ZULU0000005002', 'ZULU0000005003', ] 146 | ``` 147 | 148 | *The new series will not affect the default series, calling the series again **(with the same parameters)** will continue the numeric series.* 149 | 150 | ```php 151 | // Default Series 152 | $serial = Earmarked::get(); // Returns: '2014' 153 | $serial = Earmarked::get(3); // Returns: [ '2015', '2016', '2017', ] 154 | 155 | // Using Same Parameters Again 156 | $earmark = new Earmark('ZULU', null, 10, 5000, null); 157 | $earmark->get(); // Returns: 'ZULU0000005004' 158 | $earmark->get(3); // Returns: [ 'ZULU0000005005', 'ZULU0000005006', 'ZULU0000005007', ] 159 | ``` 160 | 161 | ## How it works 162 | 163 | Searching for gaps in a numerical series of numbers can be resource intensive. *Depends on the size of the series.* 164 | 165 | This package uses two (`2`) tables, one for the series of used numbers and one to `Hold` a group of available numbers for immediate use. The package will `get()` the next consecutive number from the `Hold` table. 166 | 167 | When the available numbers in the `Hold` table falls below one-third, this package will repopulate the `Hold` with more numbers. *This package works best with Laravel queues.* 168 | 169 | Numbers in a series that have been `unset()` will be added to the `Hold` table for *reuse* when updated. *Numbers in the `Hold` table **are not** sorted, `unset()` numbers will **eventually** be available again.* 170 | 171 | ## Additional Feature 172 | 173 | ### Auto-Increment 174 | 175 | *Sometimes* you just want the next number in an auto-increment series. One is included in this package. It *does not* support `prefix`, *will not* support `suffix`, but you **can** use zero-padding. 176 | 177 | **Do not use `unset()` with `increment()` values! You have been warned!** 178 | 179 | ```php 180 | Earmarked::increment(); // Returns: 1 181 | Earmarked::increment(); // Returns: 2 182 | $earmark = new Earmark('ZULU', null, 20, 5000, null); 183 | $earmark->increment(true); // Returns: '00000000000000000003' 184 | 185 | ``` 186 | 187 | ## Contributing 188 | 189 | Thinking of contributing? 190 | 191 | 1. Fork & clone the project: `git clone git@github.com:your-username/earmark.git`. 192 | 2. Run the tests and make sure that they pass with your setup: `phpunit`. 193 | 3. Create your bugfix/feature branch and code away your changes. Add tests for your changes. 194 | 4. Make sure all the tests still pass: `phpunit`. 195 | 5. Push to your fork and submit new a pull request. 196 | -------------------------------------------------------------------------------- /source/Http/Controllers/Serial.php: -------------------------------------------------------------------------------- 1 | model = config('earmark.model'); 54 | 55 | $this->prefix = $altPrefix ?: config('earmark.prefix'); 56 | $this->suffix = $altSuffix ?: config('earmark.suffix'); 57 | 58 | $this->digit = config('earmark.columns.digit'); 59 | $this->group = config('earmark.columns.group'); 60 | $this->min = ! is_null($altMin) ? $altMin : config('earmark.range.min'); 61 | $this->max = ! is_null($altMax) ? $altMax : config('earmark.range.max'); 62 | $this->padding = ! is_null($altPadding) ? $altPadding : config('earmark.padding'); 63 | 64 | $this->initHold(); 65 | } 66 | 67 | public function get($count = null) 68 | { 69 | if ($count) { 70 | $data = []; 71 | $i = 1; 72 | 73 | for ($i; $i <= $count; $i++) { 74 | $data[] = $this->affix($this->getEarMark()); 75 | } 76 | //event(new EarMarkRefill()); 77 | EarmarkQueue::dispatch($this->prefix, $this->suffix, $this->padding, $this->min, $this->max); 78 | 79 | return $data; 80 | } else { 81 | // event(new EarMarkRefill()); 82 | EarmarkQueue::dispatch($this->prefix, $this->suffix, $this->padding, $this->min, $this->max); 83 | 84 | return $this->affix($this->getEarMark()); 85 | } 86 | } 87 | 88 | public function unset($value) 89 | { 90 | $model = config('earmark.model'); 91 | 92 | if (is_array($value)) { 93 | foreach ($value as $data) { 94 | $model::where($this->group, $this->prefix)-> 95 | //where($this->suffix_col,$this->suffix)-> 96 | where($this->digit, $this->unfix($data))-> 97 | delete(); 98 | } 99 | } else { 100 | if (! empty($value)) { 101 | //$data $this->unfix($data); 102 | 103 | return $model::where($this->group, $this->prefix)-> 104 | //where($this->suffix_col,$this->suffix)-> 105 | where($this->digit, $this->unfix($value))-> 106 | delete(); 107 | } 108 | } 109 | } 110 | 111 | private function affix($number) 112 | { 113 | $data = $this->zeroPadding($number); 114 | 115 | return $this->prefix.$data; // . $this->suffix; 116 | } 117 | 118 | private function unfix($number) 119 | { 120 | if ($this->prefix) { 121 | $data = ltrim($number, $this->prefix); 122 | } else { 123 | $data = $number; 124 | } 125 | 126 | //return rtrim($data,$this->suffix); 127 | return $data; 128 | } 129 | 130 | /* 131 | private function unfix($number) 132 | { 133 | $data = ltrim($number, $this->prefix); 134 | 135 | //return rtrim($data,$this->suffix); 136 | return $data; 137 | } 138 | */ 139 | 140 | public function getMax() 141 | { 142 | $model = config('earmark.model'); 143 | 144 | return max([ 145 | $model::where($this->group, $this->prefix)->max($this->digit), 146 | $this->min, 147 | ]); 148 | } 149 | 150 | private function zeroPadding($value) 151 | { 152 | return str_pad($value, $this->padding, '0', STR_PAD_LEFT); 153 | } 154 | 155 | public function increment($fill = false) 156 | { 157 | $model = config('earmark.accrual'); 158 | $inc = new $model; 159 | $inc->save(); 160 | 161 | return $fill ? $this->zeroPadding($inc->id) : $inc->id; 162 | } 163 | 164 | private function generateHold() 165 | { 166 | $model = config('earmark.model'); 167 | $hold = config('earmark.hold_model'); 168 | 169 | $max = $this->getMax() + config('earmark.hold'); 170 | 171 | $n = 1; 172 | 173 | for ($i = $this->min; $i <= $max; $i++) { 174 | $used = $model::where( 175 | $this->group, 176 | $this->prefix 177 | )->where( 178 | $this->digit, 179 | $i 180 | )->count(); 181 | 182 | $unused = $hold::where( 183 | $this->group, 184 | $this->prefix 185 | )->where( 186 | $this->digit, 187 | $i 188 | )->count(); 189 | 190 | if ((! $used) && (! $unused)) { 191 | if ($n <= config('earmark.hold')) { 192 | $this->insertHold($i); 193 | $n++; 194 | } else { 195 | break; 196 | } 197 | } 198 | } 199 | } 200 | 201 | public function refill() 202 | { 203 | Log::info('Check Refill'); 204 | 205 | $hold = config('earmark.hold_model'); 206 | $count = $this->checkHold(); 207 | $floor = config('earmark.hold') / 3; 208 | if ($count < $floor) { 209 | Log::info('Earmark Refilled for '.$this->prefix); 210 | 211 | $this->generateHold(); 212 | } 213 | } 214 | 215 | private function checkHold() 216 | { 217 | $hold = config('earmark.hold_model'); 218 | 219 | return $hold::where($this->group, $this->prefix)->count(); 220 | } 221 | 222 | private function initHold() 223 | { 224 | //Log::debug('Earmark Hold Intilized'); 225 | if ($this->checkHold() == 0) { 226 | $this->generateHold(); 227 | Log::info('Earmark Hold Intilized for '.$this->prefix); 228 | } 229 | } 230 | 231 | private function checkEmpty() 232 | { 233 | //Log::debug('Earmark Hold Intilized'); 234 | if ($this->checkHold() == 0) { 235 | $this->generateHold(); 236 | Log::warning('Earmark hold size of ('.config('earmark.hold').") exceeded. \nQueue bypassed"); 237 | } 238 | } 239 | 240 | private function getEarMark() 241 | { 242 | // $this->initHold(); 243 | 244 | $hold = config('earmark.hold_model'); 245 | $model = config('earmark.model'); 246 | 247 | $digit = $this->digit; 248 | $group = $this->group; 249 | 250 | $data = null; 251 | 252 | // Generate if Hold is EMPTY 253 | $this->checkEmpty(); 254 | 255 | DB::transaction( 256 | function () use (&$hold, &$model, &$digit, &$group, &$data 257 | ) { 258 | $pull = $hold::where( 259 | $this->group, 260 | $this->prefix 261 | )->first(); 262 | $data = $pull->digit; 263 | $hold::destroy($pull->id); 264 | 265 | $push = new $model; 266 | $push->digit = $data; 267 | $push->$group = $this->prefix; 268 | $push->save(); 269 | }); 270 | 271 | //if ($this->checkHold() < config('earmark.hold')) 272 | //event(new EarMarkRefill()); 273 | // $this->prefix, $this->suffix, $this->padding, $this->min, $this->max 274 | //EarmarkQueue::dispatch($this->prefix, $this->suffix, $this->padding, $this->min, $this->max); 275 | 276 | return $data; 277 | } 278 | 279 | private function insertHold($newDigit) 280 | { 281 | $model = config('earmark.hold_model'); 282 | 283 | $digit = $this->digit; 284 | $group = $this->group; 285 | 286 | $data = new $model; 287 | $data->digit = $newDigit; 288 | $data->$group = $this->prefix; 289 | $data->save(); 290 | } 291 | 292 | // Poing\Earmark\Models\EarMark::max('digit') 293 | // Poing\Earmark\Models\EarMark::where('type','alpha')->max('digit') 294 | } 295 | --------------------------------------------------------------------------------