webnuvola/laravel-json-ld
Laravel JSON-LD makes it easy to generate Schema.org structured data in your applications. Build and output rich, search-engine-friendly JSON-LD markup with a fluent API, so you can add valid structured data to your views and improve SEO without hand-writing scripts.
Check the runtime floor first. v2.2.0 drops PHP 8.1 and 8.2 and Laravel 9, so the package now requires PHP >= 8.3 and Laravel >= 10 (with 12 and 13 explicitly supported). Before anything else, confirm your app and CI images satisfy that:
php -v
composer show laravel/framework | grep versions
composer why-not vendor/package 2.2.0 # tells you exactly which constraint blocks the upgrade
Install / upgrade:
composer require vendor/package:^2.2
php artisan vendor:publish --provider="Vendor\Package\PackageServiceProvider" # config, migrations, views
php artisan migrate
Where to look first, in this order:
config/<package>.php — nearly every behaviour toggle lives here; read it top to bottom before writing code.tests/ directory — the fastest source of real, working usage examples (faster than the README).CHANGELOG.md / UPGRADE.md for the 2.x breaking-change notes.First use case: resolve the package's main entry point from the container inside a throwaway route or php artisan tinker session and run the single happy-path call end to end — config published, migrations run, one record read and written — before wiring it into a feature. This flushes out missing publishes and migration ordering in minutes rather than mid-feature.
Inject, don't instantiate. Resolve the package's contract/class from the container rather than new-ing it. That keeps you compatible if the package changes its constructor between minors, and lets you swap implementations in tests.
public function __construct(private readonly PackageContract $package) {}
Wrap it in a thin adapter. Create one app-level service (e.g. App\Services\BillingGateway) that owns all calls into the package. Everything else in your codebase depends on your class. When a future minor renames a method, you patch one file instead of grepping the repo.
Prefer constructor injection over the facade in anything you unit test. Use the facade for tinker and one-off scripts only. If you want ergonomics and testability, define your own facade over your adapter.
Drive behaviour through config, not code. Set values in config/<package>.php from env() in the config file itself, then override per-environment (config/testing/... or a if ($this->app->environment('testing')) block in a service provider). This survives config:cache and keeps behaviour greppable.
Test with fakes/bindings, not the network. In setUp() of your test case:
$this->app->instance(PackageContract::class, new FakePackage());
If the package makes HTTP calls, wrap them in Http::fake(). Use RefreshDatabase if it ships migrations, and assert on your adapter's public behaviour rather than the vendor's internals.
Long-running work goes on the queue. If the package exposes a job or a "sync/async" flag, default to queued in production and sync in tests. Anything touching a third-party API inside a web request is a timeout waiting to happen.
Upgrade in a branch with the full suite green. Bump the constraint, run composer update vendor/package --with-dependencies, run tests, then read the diff of the published config (php artisan vendor:publish --force into a scratch dir and diff) — new keys with non-obvious defaults are the usual silent-breakage source.
The 8.1/8.2 and Laravel 9 drops are the real breaking change here. It's a composer-constraint break, not an API break — your app will fail to install, which is the good failure mode. Update the CI matrix (PHP versions and Laravel versions) in the same PR as the constraint bump, or you'll get green local builds and red pipelines.
PHP 8.4/8.5 deprecations surface as noise, not errors. Implicitly-nullable parameters (Type $x = null instead of ?Type $x = null) and various E_DEPRECATED notices will start filling logs once you move to 8.4+. Run the suite with error_reporting(E_ALL) and a deprecation-to-exception handler at least once, and treat any deprecation originating inside the package as a signal to check for a 2.3.x patch.
Laravel 12/13 support doesn't mean you should jump. If your app is on Laravel 11, stay on the package minor that supports it rather than forcing both upgrades at once. One variable at a time: framework first, then package.
env() outside config files returns null after config:cache. Classic post-deploy "works locally, broken in prod." Any package setting read at runtime must funnel through a config key.
Config merge vs. publish. Most packages mergeConfigFrom(), so your published config silently misses new keys added in 2.2.0 — the defaults apply and nothing warns you. After every minor upgrade, diff the package's shipped config against yours.
Publish tags are per-artifact. vendor:publish without a --tag republishes everything and can clobber your edited config. Always target --tag=package-config (or equivalent) and never --force in production.
Extension points to know: container bindings (swap the implementation in a service provider), Model::macro/Builder::extend if the package registers query scopes, custom drivers registered via the package's manager extend() method, and published views you can override under resources/views/vendor/<package>/.
Debugging toolkit: php artisan about for environment sanity, composer show vendor/package for the resolved version and its requirements, app()->bound(Contract::class) to confirm registration order (a service provider registered after yours won't be visible in your register()), and Laravel Telescope for anything that fires events or jobs.
Pin ^2.2 and commit the lock file. Given the runtime floor moved, a floating * constraint on a shared library or a CI cache that resolves fresh will eventually pull a version your production PHP can't run.
How can I help you explore Laravel packages today?