lenius / laravel-ecommerce
Shopping basket package for Laravel
Requires
- php: ^8.3
- illuminate/auth: ^11.0|^12.0|^13.0
- illuminate/container: ^11.0|^12.0|^13.0
- illuminate/contracts: ^11.0|^12.0|^13.0
- illuminate/database: ^11.0|^12.0|^13.0
- lenius/basket: ^5.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.4
- larastan/larastan: ^2.11|^3.10
- orchestra/testbench: ^9.0|^10.0|^11.0
- phpunit/phpunit: ^10.5|^11.5|^12.5|^13.0
README
Installation
This package supports Laravel 11 through 13 and PHP 8.3 through 8.5.
You can install this package via composer using:
composer require lenius/laravel-ecommerce
You can then export the configuration:
php artisan vendor:publish --provider="Lenius\LaravelEcommerce\EcommerceServiceProvider" --tag="config" php artisan vendor:publish --provider="Lenius\LaravelEcommerce\EcommerceServiceProvider" --tag="lang" php artisan vendor:publish --provider="Lenius\LaravelEcommerce\EcommerceServiceProvider" --tag="views"
Overview
Look at one of the following topics to learn more
Storage drivers
The package uses Laravel's session storage by default. Set the storage driver
to database when carts must survive session expiration or be available to
other application processes.
Database storage
Publish the package configuration if it has not already been published:
php artisan vendor:publish --provider="Lenius\LaravelEcommerce\EcommerceServiceProvider" --tag="config"
If the application already contains a previously published
config/ecommerce.php, add the new storage, database, and cookie settings
manually or republish the file after backing up local changes.
Select the database driver in .env:
ECOMMERCE_STORAGE=database
The package migration is always registered, regardless of the selected storage
driver. Run the normal application migrations to create the
ecommerce_carts table:
php artisan migrate
This means applications that keep the default session driver also get the cart
table when php artisan migrate is run. The table remains unused until the
database storage driver is selected.
Every cart is stored in one row. The items column contains the complete cart
as JSON, while identifier contains the UUID from the cart cookie. Item data
must therefore be JSON serializable. The table also contains an optional
authenticated user_id, expiration timestamp, and an internal version
number used to detect concurrent updates.
The database driver uses Laravel's standard database.default connection. Set
ECOMMERCE_DB_CONNECTION only when carts should use another configured
connection. The table name, cart expiration, and cookie lifetime can also be
configured through environment variables:
ECOMMERCE_STORAGE=database ECOMMERCE_DB_CONNECTION=mysql ECOMMERCE_DB_TABLE=ecommerce_carts ECOMMERCE_CART_EXPIRATION=43200 ECOMMERCE_CART_CONFLICT_RETRIES=3 ECOMMERCE_COOKIE_MINUTES=43200 ECOMMERCE_CART_PRUNE_DAYS=30 ECOMMERCE_CART_PRUNE_CRON="0 3 * * *"
The expiration values are measured in minutes; 43200 is 30 days. Set cart
expiration to 0 to store no expiration timestamp. The expiration timestamp
is refreshed when the cart changes. A read-only load refreshes it only after
at least half of the configured lifetime has passed, avoiding a database write
on every page view. The cookie lifetime should normally be at least as long as
the database cart lifetime so a guest can find the same cart again.
When an authenticated user accesses a database cart, the current user ID is stored on the row. This iteration does not automatically merge multiple carts belonging to the same user.
Database writes use optimistic locking. If another request changes the same
cart, the driver reloads the latest row and retries the requested mutation up
to three times by default. Configure the limit with
ECOMMERCE_CART_CONFLICT_RETRIES. If all attempts conflict, the driver throws
Lenius\LaravelEcommerce\Exceptions\CartConflictException, which Laravel
renders as an HTTP 409 Conflict response.
Pruning expired carts
Expired carts are not deleted automatically as soon as they expire, so that,
for example, an abandoned-cart follow-up job can still read the user_id
stamped on an expired row. The package registers an ecommerce:prune-carts
Artisan command on the application's schedule to permanently delete carts
whose expires_at lies more than ECOMMERCE_CART_PRUNE_DAYS (default 30)
days in the past. Rows with no expiration (ECOMMERCE_CART_EXPIRATION=0) are
never pruned.
ECOMMERCE_CART_PRUNE_DAYS=30 ECOMMERCE_CART_PRUNE_CRON="0 3 * * *" ECOMMERCE_CART_PRUNE_CHUNK_SIZE=500
Set ECOMMERCE_CART_PRUNE_DAYS=0 to disable pruning entirely; the command
is then also removed from the schedule. ECOMMERCE_CART_PRUNE_CRON accepts
any standard cron expression and defaults to once a day at 03:00. Rows are
deleted in batches of ECOMMERCE_CART_PRUNE_CHUNK_SIZE (default 500)
rather than a single unbounded DELETE, so a large backlog does not hold a
long-running lock against the table. The schedule entry uses
onOneServer() and withoutOverlapping(), so it is safe to run on every
node of a multi-server deployment and won't stack runs if one takes longer
than expected. Pruning can also be run manually or from your own scheduler:
php artisan ecommerce:prune-carts
This relies on Laravel's own scheduler, so the host application must have its usual single cron entry configured, e.g.:
* * * * * cd /path/to/app && php artisan schedule:run >> /dev/null 2>&1
Without that entry, the prune command is registered but will never run.
Custom cart items
The default database item factory restores JSON data as
Lenius\Basket\Item. Applications that insert a custom ItemInterface with
overridden behavior should provide their own factory so the same class is used
after the next request:
namespace App\Basket; use App\BasketItem; use Lenius\Basket\ItemInterface; use Lenius\LaravelEcommerce\Contracts\ItemFactoryInterface; class BasketItemFactory implements ItemFactoryInterface { public function create(array $data, string $identifier): ItemInterface { $item = new BasketItem($data); $item->setIdentifier($identifier); return $item; } }
Bind the factory in the application's service provider:
use App\Basket\BasketItemFactory; use Lenius\LaravelEcommerce\Contracts\ItemFactoryInterface; public function register(): void { $this->app->bind( ItemFactoryInterface::class, BasketItemFactory::class, ); }
Applications using the standard Lenius\Basket\Item do not need a custom
binding.
Usage
The shoppingcart gives you the following methods to use:
Cart::insert()
Adding an item to the cart is really simple, you just use the insert() method, which accepts a variety of parameters.
In its most basic form you can specify the id, name, quantity, price of the product you'd like to add to the cart.
Cart::insert(new Item([ 'id' => 'foo', 'name' => 'bar', 'price' => 100, 'quantity' => 2, 'weight' => 300 ]));
Cart::insert() accept a class which implements ItemInterface
class CustomItem implements ItemInterface { }
Inserting items with options into the cart
Inserting an item into the cart is easy. The required keys are id, name, price and quantity, although you can pass over any custom data that you like. If option items contains price or weight there values are added to the total weight / price of the product.
Cart::insert(new Item([ 'id' => 'foo', 'name' => 'bar', 'price' => 100, 'quantity' => 2, 'weight' => 300, 'options' => [ [ 'name' => 'Size', 'value' => 'L', 'weight' => 50, 'price' => 10 ], ], ]));
Setting the tax rate for an item
Another key you can pass to your insert method is tax'. This is a percentage which you would like to be added onto the price of the item.
In the below example we will use 25% for the tax rate.
Cart::insert(new Item([ 'id' => 'mouseid', 'name' => 'Mouse', 'price' => 100, 'quantity' => 1, 'tax' => 25, 'weight' => 200 ]));
Updating items in the cart
Use Cart::update() so the selected storage driver persists the change. For
example, when iterating over the cart contents:
foreach (Cart::contents() as $item) { Cart::update($item->identifier, 'name', 'Foo'); Cart::update($item->identifier, 'quantity', 1); }
Destroying/emptying the cart
You can completely empty/destroy the cart by using the destroy() method.
Cart::destroy()
Retrieve the cart contents
You can loop the cart contents by using the following method
Cart::contents();
You can also return the Cart items as an array by passing true as the first argument
Cart::contents(true);
Check if the Cart has an item
Cart::has($itemIdentifier);
Remove an item from the Cart
Cart::remove($itemIdentifier)
Increment an item from the Cart
Cart::inc($itemIdentifier)
Decrement an item from the Cart
Cart::dec($itemIdentifier)
Events
The cart also has events build in. There are five events available for you to listen for.
| Event | Fired | Parameter |
|---|---|---|
| CartItemUpdated | When an item in the cart was updated. | The CartItem that was updated. |
| CartItemRemoved | When an item is removed from the cart. | The CartItem that was removed. |
| CartItemDecreased | When an item is dec from the cart. | The CartItem that was decreased. |
| CartItemIncremented | When an item is inc from the cart. | The CartItem that was incremented. |
| CartDestroyed | When the cart was destroyed. | - |
Containerized development
The ./app helper runs the development tools in a single PHP CLI container.
It does not start a web server, database, or other services. The image contains
PHP, Composer, Git, unzip, and the PHP extensions required by Laravel, PHPUnit,
and the package dependencies.
Docker with Linux container support is required. Use ./app from Bash or
app.ps1 directly from Windows PowerShell. Make the Bash helper executable
after cloning if necessary:
chmod +x app
Windows quick start
Install and start Docker Desktop and make sure it is using Linux containers. Then open PowerShell in the project directory. On a fresh clone, build the test container, install the Composer dependencies, and run all checks:
Set-Location C:\path\to\laravel-ecommerce .\app.ps1 build .\app.ps1 composer-install .\app.ps1 check
The build creates the local image laravel-ecommerce-dev:php-8.5. The
check command runs strict Composer validation, PHPUnit, and PHPStan. Source
files and the vendor directory are shared between Windows and the container
through the project mount.
An explicit build is optional after the initial dependency installation. If an
image is missing, commands such as .\app.ps1 check build it automatically.
To rebuild the image from the latest base image without using Docker's build cache, run:
.\app.ps1 build --force
Build and test with another supported PHP version by placing --php before the
command. Every PHP version uses a separate Docker image:
.\app.ps1 --php 8.4 build .\app.ps1 --php 8.4 composer-update .\app.ps1 --php 8.4 check
PHP 8.5 is the default, so --php 8.5 can normally be omitted. Run
composer-update after changing PHP versions if the existing dependencies were
resolved on a different PHP version.
If the Windows execution policy blocks local scripts, either allow scripts for the current PowerShell process:
Set-ExecutionPolicy -Scope Process Bypass .\app.ps1 check
or start the script with a process-scoped bypass without changing the machine policy:
powershell -NoProfile -ExecutionPolicy Bypass -File .\app.ps1 check
Bash quick start
On Linux, macOS, or in WSL, the equivalent fresh-clone workflow is:
./app build ./app composer-install ./app check
The default image is built automatically when it is missing. Force a clean rebuild with:
./app build --force
Use PHP 8.4 explicitly with:
./app --php 8.4 build ./app --php 8.4 composer-update ./app --php 8.4 check
The project is mounted at /app, so source changes and Composer changes are
written directly to the working tree. Composer's download cache is persisted
in ~/.cache/composer on the host.
Available commands
| Command | Description |
|---|---|
./app build [--force] |
Build or rebuild the PHP tool image. |
./app check |
Run Composer validation, PHPUnit, and PHPStan. |
./app test [arguments] |
Run PHPUnit and forward optional arguments. |
./app stan [arguments] |
Run PHPStan and forward optional arguments. |
./app php-cs-fixer [arguments] |
Run PHP CS Fixer and apply fixes. |
./app composer-install [arguments] |
Install Composer dependencies. |
./app composer-update [arguments] |
Update Composer dependencies. |
./app composer-validate [arguments] |
Validate composer.json strictly. |
./app composer-outdated [arguments] |
List outdated Composer dependencies. |
./app composer [arguments] |
Run any Composer command. |
./app php [arguments] |
Run any PHP command. |
./app php-shell |
Open an interactive Bash shell in the PHP container. |
./app help |
Display the command overview. |
Arguments are forwarded to the underlying tool. For example:
./app test --filter CartTest
./app php -v
./app composer show --direct
./app composer check-platform-reqs
The PowerShell helper accepts the same commands and arguments. Replace ./app
with .\app.ps1:
.\app.ps1 test --filter CartTest .\app.ps1 php -v .\app.ps1 composer check-platform-reqs
Select another supported PHP version with --php. Each version gets its own
local Docker image:
./app --php 8.4 build
./app --php 8.4 composer-update
./app --php 8.4 test
The equivalent PowerShell syntax is .\app.ps1 --php 8.4 test.
Running without Docker
The equivalent local Composer commands are:
composer validate --strict
composer test
composer analyse
composer fix
Contributing
Please see CONTRIBUTING for details.
Security
If you discover any security-related issues, please email info@lenius.dk instead of using the issue tracker.
License
The MIT License (MIT). Please see License File for more information.