├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── config └── external-references.php ├── configure.php ├── database └── migrations │ └── create_external_references_table.php.stub ├── resources └── views │ └── .gitkeep └── src ├── LaravelExternalReferencesServiceProvider.php ├── Models └── ExternalReference.php └── Traits └── HasExternalReferences.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `laravel-external-references` will be documented in this file. 4 | 5 | ## Added caching logic - 2024-09-08 6 | 7 | ### Release Note: Caching Feature for External References 8 | 9 | We are excited to announce the introduction of a new **caching feature** for external references in this release. This enhancement improves the performance and efficiency of retrieving external references by storing frequently accessed data in cache, reducing database load and speeding up response times. 10 | 11 | #### Key Features: 12 | 13 | - **Automatic Caching**: External references are now automatically cached when `external-references.caching` is enabled in the configuration. 14 | - **Custom Cache Lifespan**: Control the cache duration via the `external-references.cache_lifespan` setting, ensuring data is updated as frequently as needed. 15 | - **Provider & Tag-based Caching**: Cache keys are dynamically generated based on the provider, tag, and associated entity, allowing unique cache entries for each combination. 16 | - **Graceful Fallback**: If caching is disabled, the system will continue to function normally, querying the database directly for external references. 17 | 18 | #### How It Works: 19 | 20 | When enabled, the system checks for cached external references before querying the database. If the reference is not found in cache, it retrieves the reference from the database and stores it in cache for future use. This ensures that subsequent requests are served faster by fetching data directly from the cache. 21 | 22 | ## Ready for Usage - 2024-05-19 23 | 24 | This package is now ready for usage 25 | 26 | ## First Release - 2024-05-15 27 | 28 | First release of laravel-external-references 29 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Ayzerobug 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | # Manage External References in Laravel 7 | 8 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/ayzerobug/laravel-external-references.svg?style=flat-square)](https://packagist.org/packages/ayzerobug/laravel-external-references) 9 | [![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/ayzerobug/laravel-external-references/run-tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/ayzerobug/laravel-external-references/actions?query=workflow%3Arun-tests+branch%3Amain) 10 | [![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/ayzerobug/laravel-external-references/fix-php-code-style-issues.yml?branch=main&label=code%20style&style=flat-square)](https://github.com/ayzerobug/laravel-external-references/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amain) 11 | [![Total Downloads](https://img.shields.io/packagist/dt/ayzerobug/laravel-external-references.svg?style=flat-square)](https://packagist.org/packages/ayzerobug/laravel-external-references) 12 | 13 | This package facilitates the seamless integration of your Laravel Models with external systems or services by managing external references or identifiers. It streamlines the process of associating your application's data with external datasets, such as payment processor IDs or user accounts. This enhancement enables the smooth integration of your application with diverse services and systems, thereby augmenting its capabilities and adaptability. 14 | 15 | ## Installation 16 | 17 | You can install the package via composer: 18 | 19 | ```bash 20 | composer require ayzerobug/laravel-external-references 21 | ``` 22 | 23 | You can publish and run the migrations with: 24 | 25 | ```bash 26 | php artisan vendor:publish --tag="external-references-migrations" 27 | php artisan migrate 28 | ``` 29 | 30 | You can publish the config file with: 31 | 32 | ```bash 33 | php artisan vendor:publish --tag="external-references-config" 34 | ``` 35 | 36 | 41 | 42 | ## Usage 43 | 44 | Include the HasExternalReferences trait in your model: 45 | 46 | ```php 47 | namespace App\Models; 48 | 49 | use Ayzerobug\LaravelExternalReferences\Traits\HasExternalReferences; 50 | 51 | class Payment extends Model 52 | { 53 | use HasExternalReferences; 54 | 55 | ... 56 | } 57 | 58 | ``` 59 | 60 | Set Payment External Reference: 61 | 62 | ```php 63 | use App\Models\Payment; 64 | 65 | $payment = Payment::find($id); 66 | $idOnPaystack = "random-id"; 67 | $payment->setExternalReference($idOnPaystack, 'paystack'); 68 | ``` 69 | 70 | Get the external Reference 71 | 72 | ```php 73 | use App\Models\Payment; 74 | 75 | $payment = Payment::find($id); 76 | $idOnPaystack = $payment->getExternalReference('paystack'); 77 | ``` 78 | 79 | Get Payment with the external Reference 80 | 81 | ```php 82 | use App\Models\Payment; 83 | 84 | $idOnPaystack = "random-id"; 85 | $payment = Payment::findByExternalReference($idOnPaystack, 'paystack'); 86 | ``` 87 | 88 | ## Changelog 89 | 90 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 91 | 92 | ## Contributing 93 | 94 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 95 | 96 | ## Security Vulnerabilities 97 | 98 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 99 | 100 | ## Credits 101 | 102 | - [Ayomide Micheal](https://github.com/ayzerobug) 103 | - [All Contributors](../../contributors) 104 | 105 | ## License 106 | 107 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 108 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ayzerobug/laravel-external-references", 3 | "description": "Easy link Laravel Models with external references for seamless integration and enhanced functionality.", 4 | "keywords": [ 5 | "Ayzerobug", 6 | "laravel", 7 | "laravel-external-references" 8 | ], 9 | "homepage": "https://github.com/ayzerobug/laravel-external-references", 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Ayomide Micheal", 14 | "email": "aydcoder@gmail.com", 15 | "role": "Developer" 16 | } 17 | ], 18 | "require": { 19 | "php": "^8.2", 20 | "spatie/laravel-package-tools": "^1.16", 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||^3.0", 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 | "spatie/laravel-ray": "^1.35" 35 | }, 36 | "autoload": { 37 | "psr-4": { 38 | "Ayzerobug\\LaravelExternalReferences\\": "src/" 39 | } 40 | }, 41 | "autoload-dev": { 42 | "psr-4": { 43 | "Ayzerobug\\LaravelExternalReferences\\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-external-references --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 | "Ayzerobug\\LaravelExternalReferences\\LaravelExternalReferencesServiceProvider" 76 | ], 77 | "aliases": { 78 | "LaravelExternalReferences": "Ayzerobug\\LaravelExternalReferences\\Facades\\LaravelExternalReferences" 79 | } 80 | } 81 | }, 82 | "minimum-stability": "stable", 83 | "prefer-stable": true 84 | } 85 | -------------------------------------------------------------------------------- /config/external-references.php: -------------------------------------------------------------------------------- 1 | ['referenceable_type', 'referenceable_id', 'created_at', 'updated_at'], 17 | 18 | /* 19 | |---------------------------------------------------------------------------------- 20 | | Caching 21 | |---------------------------------------------------------------------------------- 22 | | 23 | | In some cases where you have many logics using this package and you don't want all 24 | | requests to hit your database, you can enable caching to cache the value of 25 | | the identifier for a while. 26 | | 27 | */ 28 | 'caching' => (bool) env('EXTERNAL_REFERENCES_CACHING', false), 29 | 30 | /* 31 | |---------------------------------------------------------------------------------- 32 | | Cache lifespan 33 | |---------------------------------------------------------------------------------- 34 | | 35 | | With this option, you can set the cache lifespan in seconds. This defines how long the 36 | | cache will retain the value of the reference before querying the database again. 37 | | 38 | */ 39 | 'cache_lifespan' => 30, 40 | 41 | ]; 42 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /database/migrations/create_external_references_table.php.stub: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->timestamps(); 14 | $table->morphs('referenceable'); 15 | $table->string('provider')->index('provider_idx'); 16 | $table->string('reference')->index('reference_idx'); 17 | $table->string('tag')->nullable(); 18 | }); 19 | } 20 | 21 | public function down(): void 22 | { 23 | Schema::dropIfExists('external_references'); 24 | } 25 | }; -------------------------------------------------------------------------------- /resources/views/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayzerobug/laravel-external-references/99d78c3ac4ba55d59a849e3b3a3925b04393782a/resources/views/.gitkeep -------------------------------------------------------------------------------- /src/LaravelExternalReferencesServiceProvider.php: -------------------------------------------------------------------------------- 1 | name('laravel-external-references') 16 | ->hasConfigFile() 17 | ->hasMigration('create_external_references_table'); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Models/ExternalReference.php: -------------------------------------------------------------------------------- 1 | hidden = config('external-references.hidden_attributes', []); 23 | } 24 | 25 | public function referenceable(): MorphTo 26 | { 27 | return $this->morphTo(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Traits/HasExternalReferences.php: -------------------------------------------------------------------------------- 1 | morphMany(ExternalReference::class, 'referenceable'); 19 | } 20 | 21 | /** 22 | * Set a new external reference. 23 | * 24 | * @return $this 25 | */ 26 | public function setExternalReference(string $reference, string $provider, ?string $tag = null): self 27 | { 28 | $this->externalReferences()->updateOrCreate( 29 | ['provider' => $provider, 'tag' => $tag], 30 | ['reference' => $reference] 31 | ); 32 | 33 | return $this; 34 | } 35 | 36 | /** 37 | * Get the first reference for a given provider and tag. 38 | */ 39 | public function getExternalReference(string $provider, ?string $tag = null): ?string 40 | { 41 | $cacheEnabled = config('external-references.caching', false); 42 | $cacheLifespan = config('external-references.cache_lifespan', 20); 43 | $cacheKey = "external_references.provider:$provider,".static::class.":{$this->id},tag:$tag"; 44 | 45 | $callback = function () use ($provider, $tag) { 46 | return $this->externalReferences() 47 | ->where('provider', $provider) 48 | ->where('tag', $tag)->first()?->reference; 49 | }; 50 | 51 | if ($cacheEnabled) { 52 | return Cache::remember($cacheKey, now()->addSeconds($cacheLifespan), $callback); 53 | } 54 | 55 | return $callback(); 56 | } 57 | 58 | /** 59 | * Find the model by external reference. 60 | */ 61 | public static function findByExternalReference(string $reference, string $provider, ?string $tag = null): ?static 62 | { 63 | $referenceableType = static::class; 64 | 65 | $externalReference = ExternalReference::where([ 66 | 'reference' => $reference, 67 | 'referenceable_type' => $referenceableType, 68 | 'provider' => $provider, 69 | 'tag' => $tag, 70 | ])->first(); 71 | 72 | return $externalReference?->referenceable; 73 | } 74 | } 75 | --------------------------------------------------------------------------------