Skip to content

Commit 65d8b5b

Browse files
authored
Support Laravel 13 and improve basket operations (#6)
* Support Laravel 13 and improve basket operations * Fix cross-version test fixture setup * Allow intentional historical Laravel CI rows * Use PHPUnit 6 for Laravel 5.5 tests * Use cross-version artisan test calls
1 parent 6c65448 commit 65d8b5b

14 files changed

Lines changed: 251 additions & 103 deletions

.coveralls.yml

Lines changed: 0 additions & 3 deletions
This file was deleted.

.github/workflows/tests.yml

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
permissions:
8+
contents: read
9+
10+
jobs:
11+
test:
12+
name: PHP ${{ matrix.php }} / Laravel ${{ matrix.laravel }}
13+
runs-on: ubuntu-latest
14+
strategy:
15+
fail-fast: false
16+
matrix:
17+
include:
18+
- php: '7.1'
19+
laravel: '5.5'
20+
testbench: '~3.5.0'
21+
phpunit: '^6.5'
22+
dbal: '^2.13'
23+
composer: v2.2
24+
- php: '7.2'
25+
laravel: '6'
26+
testbench: '^4.0'
27+
phpunit: '^8.5'
28+
dbal: '^2.13'
29+
composer: v2.2
30+
- php: '7.2'
31+
laravel: '7'
32+
testbench: '^5.0'
33+
phpunit: '^8.5'
34+
dbal: '^2.13'
35+
composer: v2.2
36+
- php: '7.3'
37+
laravel: '8'
38+
testbench: '^6.0'
39+
phpunit: '^9.6'
40+
dbal: '^2.13'
41+
composer: v2.2
42+
- php: '8.0'
43+
laravel: '9'
44+
testbench: '^7.0'
45+
phpunit: '^9.6'
46+
dbal: '^3.0'
47+
composer: latest
48+
- php: '8.1'
49+
laravel: '10'
50+
testbench: '^8.0'
51+
phpunit: '^10.5'
52+
dbal: '^3.0'
53+
composer: latest
54+
- php: '8.2'
55+
laravel: '11'
56+
testbench: '^9.0'
57+
phpunit: '^11.5'
58+
dbal: '^3.0'
59+
composer: latest
60+
- php: '8.2'
61+
laravel: '12'
62+
testbench: '^10.0'
63+
phpunit: '^11.5'
64+
dbal: '^4.0'
65+
composer: latest
66+
- php: '8.5'
67+
laravel: '13'
68+
testbench: '^11.0'
69+
phpunit: '^12.5'
70+
dbal: '^4.0'
71+
composer: latest
72+
steps:
73+
- uses: actions/checkout@v4
74+
- uses: shivammathur/setup-php@v2
75+
with:
76+
php-version: ${{ matrix.php }}
77+
extensions: mbstring, pdo_sqlite
78+
tools: composer:${{ matrix.composer }}
79+
coverage: none
80+
- name: Allow intentional historical Laravel test rows
81+
if: matrix.composer == 'latest'
82+
run: composer config audit.block-insecure false
83+
- run: composer require --dev orchestra/testbench:${{ matrix.testbench }} phpunit/phpunit:${{ matrix.phpunit }} doctrine/dbal:${{ matrix.dbal }} --no-update --no-interaction
84+
- run: composer update --with-all-dependencies --prefer-dist --no-interaction --no-progress
85+
- run: vendor/bin/phpunit

.travis.yml

Lines changed: 0 additions & 11 deletions
This file was deleted.

README.md

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# 🛒 Laravel Extendable Basket
22

3-
[![Build Status](https://travis-ci.com/Jord-JD/laravel-extendable-basket.svg?branch=master)](https://travis-ci.com/Jord-JD/laravel-extendable-basket)
4-
[![Coverage Status](https://coveralls.io/repos/github/Jord-JD/laravel-extendable-basket/badge.svg?branch=master)](https://coveralls.io/github/Jord-JD/laravel-extendable-basket?branch=master)
3+
[![Tests](https://github.com/Jord-JD/laravel-extendable-basket/actions/workflows/tests.yml/badge.svg)](https://github.com/Jord-JD/laravel-extendable-basket/actions/workflows/tests.yml)
54
[![](https://img.shields.io/packagist/dt/jord-jd/laravel-extendable-basket.svg)](https://packagist.org/packages/jord-jd/laravel-extendable-basket/stats)
65

76
Laravel Extendable Basket provides several abstract classes that implement basic ecommerce basket functionality.
@@ -17,7 +16,9 @@ composer require jord-jd/laravel-extendable-basket
1716

1817
## Compatibility
1918

20-
This package supports Laravel versions 5.5 through 10 and requires PHP 8.1 or higher.
19+
This package supports Laravel 5.5 through 13. Its own runtime syntax supports PHP
20+
7.1 and later; Composer will also enforce the higher PHP minimum required by the
21+
Laravel version selected by your application.
2122

2223
## Setup
2324

@@ -45,6 +46,7 @@ Create a Basket model.
4546
<?php
4647
namespace App;
4748

49+
use Illuminate\Database\Eloquent\Relations\HasMany;
4850
use JordJD\LaravelExtendableBasket\Models\Basket as BasketModel;
4951

5052
class Basket extends BasketModel
@@ -64,6 +66,7 @@ Create a BasketItem model.
6466
<?php
6567
namespace App;
6668

69+
use Illuminate\Database\Eloquent\Relations\BelongsTo;
6770
use JordJD\LaravelExtendableBasket\Models\BasketItem as BasketItemModel;
6871

6972
class BasketItem extends BasketItemModel
@@ -92,7 +95,8 @@ namespace App;
9295
use Illuminate\Database\Eloquent\Model;
9396
use JordJD\LaravelExtendableBasket\Interfaces\Basketable;
9497

95-
class Product extends Model implements Basketable {
98+
class Product extends Model implements Basketable
99+
{
96100

97101
// ...
98102

@@ -139,7 +143,7 @@ with any Eloquent model.
139143

140144
```php
141145
// Get new basket
142-
$basket = new Basket();
146+
$basket = new Basket(); // Saved automatically when its first item is added.
143147

144148
// Get current basket
145149
$userBasketId = Auth::user()->basket_id;
@@ -156,12 +160,18 @@ used to store information about variations on a product.
156160

157161
```php
158162
$quantity = 5;
159-
$product = Product::FindOrFail(1);
163+
$product = Product::findOrFail(1);
160164

161-
$basket->add($quantity, $product);
162-
$basket->add($quantity, $product, ['colour' => 'red', 'size' => 'medium']);
165+
$item = $basket->add($quantity, $product);
166+
$variantItem = $basket->add($quantity, $product, ['colour' => 'red', 'size' => 'medium']);
163167
```
164168

169+
`add()` returns the created basket item. If the same model and identical metadata
170+
already exist in the basket, its quantity is increased and that existing item is
171+
returned. Laravel morph-map aliases are stored when the basketable model provides
172+
one. Basketable models must be saved before they are added; otherwise an
173+
`InvalidArgumentException` is thrown instead of creating an orphaned item.
174+
165175
### Getting basket items
166176

167177
Getting items from the basket and the basketable model they contain can be easily
@@ -194,6 +204,13 @@ $item = $basket->items->first();
194204
$item->delete();
195205
```
196206

207+
To remove every item in one query, call `clear()`. It returns the number of rows
208+
deleted and keeps the basket itself available for reuse.
209+
210+
```php
211+
$deletedItemCount = $basket->clear();
212+
```
213+
197214
## Getting the unit cost of a basket item
198215

199216
Getting the unit cost of a basket item just involves calling the `getPrice` method of
@@ -230,7 +247,7 @@ A `getSubtotal` method is provided in the basket class that provides the total o
230247
items in the basket. See the following example.
231248

232249
```php
233-
$subtotal = $basket->getSubtotal()
250+
$subtotal = $basket->getSubtotal();
234251
```
235252

236253
If you wish to add delivery costs or discounts, you can create a new `getTotal` method

composer.json

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@
33
"description": "🛒 Laravel Extendable Basket provides several abstract classes that implement basic ecommerce basket functionality",
44
"type": "library",
55
"require": {
6-
"php": ">=8.1",
7-
"laravel/framework": "^5.5||^6.0||^7.0||^8.0||^9.0||^10.0"
6+
"php": ">=7.1",
7+
"laravel/framework": "^5.5||^6.0||^7.0||^8.0||^9.0||^10.0||^11.0||^12.0||^13.0"
88
},
99
"require-dev": {
10-
"orchestra/testbench": "^8.0",
11-
"phpunit/phpunit": "^9.0",
12-
"php-coveralls/php-coveralls": "^2.1",
13-
"doctrine/dbal": "^1.0||^2.0||^3.0"
10+
"orchestra/testbench": "^3.5||^4.0||^5.0||^6.0||^7.0||^8.0||^9.0||^10.0||^11.0",
11+
"phpunit/phpunit": "^6.5||^7.5||^8.5||^9.6||^10.5||^11.5||^12.5",
12+
"doctrine/dbal": "^2.13||^3.0||^4.0"
1413
},
1514
"license": "LGPL-3.0-only",
1615
"authors": [
@@ -34,12 +33,17 @@
3433
"providers": [
3534
"JordJD\\LaravelExtendableBasket\\Providers\\LaravelExtendableBasketServiceProvider"
3635
]
37-
},
38-
"branch-alias": {
39-
"dev-master": "6.0-dev"
40-
}
36+
},
37+
"branch-alias": {
38+
"dev-master": "6.1-dev"
39+
}
4140
},
4241
"replace": {
4342
"divineomega/laravel-extendable-basket": "self.version"
43+
},
44+
"config": {
45+
"allow-plugins": {
46+
"kylekatarnls/update-helper": false
47+
}
4448
}
4549
}

phpunit.xml

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,8 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<phpunit backupGlobals="false"
3-
backupStaticAttributes="false"
4-
bootstrap="vendor/autoload.php"
5-
colors="true"
6-
convertErrorsToExceptions="true"
7-
convertNoticesToExceptions="true"
8-
convertWarningsToExceptions="true"
9-
processIsolation="false"
10-
stopOnFailure="false">
2+
<phpunit bootstrap="vendor/autoload.php" colors="true">
113
<testsuites>
124
<testsuite name="Unit Tests">
135
<directory suffix="Test.php">./tests/Unit</directory>
14-
<directory suffix="Test.php">./tests/Integration</directory>
156
</testsuite>
167
</testsuites>
17-
<filter>
18-
<whitelist>
19-
<directory suffix=".php">src</directory>
20-
<exclude>
21-
<directory suffix=".php">src/Examples</directory>
22-
</exclude>
23-
</whitelist>
24-
</filter>
25-
<php>
26-
27-
</php>
28-
</phpunit>
8+
</phpunit>

src/Interfaces/BasketInterface.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ public static function getNew(): self;
1212

1313
public function items(): HasMany;
1414

15+
/**
16+
* @return BasketItemInterface
17+
*/
1518
public function add(int $quantity, Basketable $basketable, array $meta = []);
1619

1720
public function getSubtotal();

src/Models/Basket.php

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
namespace JordJD\LaravelExtendableBasket\Models;
44

5+
use Illuminate\Database\Eloquent\Model;
6+
use InvalidArgumentException;
57
use JordJD\LaravelExtendableBasket\Interfaces\Basketable;
68
use JordJD\LaravelExtendableBasket\Interfaces\BasketInterface;
7-
use Exception;
8-
use Illuminate\Database\Eloquent\Model;
99

1010
abstract class Basket extends Model implements BasketInterface
1111
{
@@ -34,17 +34,29 @@ public static function getNew(): BasketInterface
3434
public function add(int $quantity, Basketable $basketable, array $meta = [])
3535
{
3636
if ($quantity < 1) {
37-
throw new Exception('Quantity is less than one.');
37+
throw new InvalidArgumentException('Quantity must be at least one.');
38+
}
39+
40+
if (!method_exists($basketable, 'getKey') || $basketable->getKey() === null) {
41+
throw new InvalidArgumentException('The basketable model must be persisted before it can be added.');
42+
}
43+
44+
if (!$this->exists) {
45+
if (!$this->save()) {
46+
throw new \RuntimeException('Unable to persist the basket before adding an item.');
47+
}
3848
}
3949

4050
foreach ($this->items as $item) {
41-
if (get_class($item->basketable) === get_class($basketable)
42-
&& $item->basketable->getKey() === $basketable->getKey()
51+
$existingBasketable = $item->basketable;
52+
if ($existingBasketable !== null
53+
&& get_class($existingBasketable) === get_class($basketable)
54+
&& $existingBasketable->getKey() === $basketable->getKey()
4355
&& $item->meta === $meta) {
4456
$item->quantity += $quantity;
4557
$item->save();
4658

47-
return;
59+
return $item;
4860
}
4961
}
5062

@@ -53,12 +65,29 @@ public function add(int $quantity, Basketable $basketable, array $meta = [])
5365
$item = new $basketItem();
5466
$item->basket_id = $this->id;
5567
$item->quantity = $quantity;
56-
$item->basketable_type = get_class($basketable);
68+
$item->basketable_type = method_exists($basketable, 'getMorphClass')
69+
? $basketable->getMorphClass()
70+
: get_class($basketable);
5771
$item->basketable_id = $basketable->getKey();
5872
$item->meta = $meta;
5973
$item->save();
6074

6175
unset($this->items);
76+
77+
return $item;
78+
}
79+
80+
/**
81+
* Remove every item from this basket.
82+
*
83+
* @return int Number of deleted basket items.
84+
*/
85+
public function clear(): int
86+
{
87+
$deleted = $this->items()->delete();
88+
unset($this->items);
89+
90+
return (int) $deleted;
6291
}
6392

6493
public function getSubtotal()

src/Models/BasketItem.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ public function setQuantity(int $quantity)
2929

3030
public function getPrice()
3131
{
32-
return $this->quantity * $this->basketable->getPrice($this->meta);
32+
$basketable = $this->basketable;
33+
if ($basketable === null) {
34+
throw new \LogicException('Cannot calculate a basket item price because its basketable model no longer exists.');
35+
}
36+
37+
return $this->quantity * $basketable->getPrice($this->meta);
3338
}
3439
}

0 commit comments

Comments
 (0)