├── .gitignore ├── LICENSE.md ├── README.md ├── composer.json ├── composer.lock ├── phpunit.xml ├── src ├── CartServiceProvider.php ├── Console │ └── Commands │ │ └── CartCleanup.php ├── Facades │ └── Cart.php ├── Models │ ├── Cart.php │ └── CartLine.php ├── config │ └── cart.php └── database │ └── migrations │ ├── 2019_12_31_151631_create_cart_table.php │ └── 2019_12_31_151654_create_cart_lines_table.php └── tests ├── CartTest.php └── bootstrap.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) Nabeel Al-almai 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 | # DBCart 2 | 3 | 4 | Shopping Cart library for Laravel > 5.5 and Laravel 6 that uses database instead of sessions to store carts. 5 | 6 | 7 | 8 | ## Features 9 | 10 | * Cart for guest users 11 | * Cart for logged in users 12 | * Guest Cart is merged with User Cart when logged in 13 | * Singleton Cart instance to avoid unnecessary database queries. But also possible to avoid signleton cart if needed. 14 | * Built on top of Eloquent Model, so easily extendable and all eloquent methods can be used. 15 | * Multiple instances of cart 16 | * Schedule expired carts for deletion 17 | 18 | ## Before installation 19 | if you already use `dbcart by Hassansin`, please skip DB migrations 20 | and publishing configuration. This package is fully compatible. 21 | ## Installation 22 | 1. Install the package via composer: 23 | ``` bash 24 | composer require nabeelalalmai/dbcart-laravel 25 | ``` 26 | 27 | The package will automatically register itself. 28 | 29 | 2. Publish database migrations(IF YOU USE dbcart: 30 | 31 | ```bash 32 | php artisan vendor:publish --provider="NabeelAlalmai\DBcart\CartServiceProvider" --tag="migrations" 33 | ``` 34 | 35 | After the migration has been published you can create the `cart` and `cart_lines` tables by running the migrations: 36 | 37 | ```bash 38 | php artisan migrate 39 | ``` 40 | 41 | 42 | 43 | ## Configuration 44 | 45 | Optionally, you can publish package config file: 46 | 47 | ```sh 48 | php artisan vendor:publish --provider="NabeelAlalmai\DBcart\CartServiceProvider" --tag=config 49 | ``` 50 | Now, update `config/cart.php` if required 51 | 52 | ## Usage 53 | 54 | #### Get Cart Instance: 55 | Get the current cart instance. It returns a singleton cart instance: 56 | ```php 57 | $cart = app('cart'); //using app() helper 58 | ``` 59 | or, 60 | 61 | ```php 62 | $cart = App::make('cart'); 63 | ``` 64 | alternatively, you can avoid singleton instance and use the model class to load the cart instance from database everytime: 65 | 66 | ```php 67 | use NabeelAlalmai\DBcart\Models\Cart; 68 | //... 69 | $cart = Cart::current(); 70 | ``` 71 | The idea of using singleton cart is that the cart object will be available globally throughout your app (e.g. controllers/models/views/view composers etc) for a single request. Also as you manipulate cart items, `$cart->item_count` and `$cart->total_price` would get updated. 72 | 73 | #### Add an Item: `$cart->addItem($attributes)` 74 | 75 | ```php 76 | $cart->addItem([ 77 | 'product_id' => 1, 78 | 'unit_price' => 10.5, 79 | 'quantity' => 1 80 | ]); 81 | ``` 82 | 83 | which is equivalent to `$cart->items()->create($attributes)` 84 | 85 | #### Get Items: `$cart->items` 86 | 87 | Since `$cart` is eloquent model instance, you can use any of the eloquent methods to get items 88 | 89 | ```php 90 | $items = $cart->items // by dynamic property access 91 | $items = $cart->items()->get() 92 | $items = $cart->items()->where('quantity', '>=', 2)->get() 93 | ``` 94 | 95 | #### Update an Item: `$cart->updateItem($where, $attributes)` 96 | 97 | ```php 98 | $cart->updateItem([ 99 | 'id' => 2 100 | ], [ 101 | 'product_id' => 1, 102 | 'unit_price' => 10.5, 103 | 'quantity' => 1 104 | ]); 105 | ``` 106 | 107 | which is equivalent to `$cart->items()->where($where)->first()->update($attributes)` 108 | 109 | #### Remove an Item: `$cart->removeItem($where)` 110 | 111 | ```php 112 | $cart->removeItem([ 113 | 'id' => 2 114 | ]); 115 | ``` 116 | 117 | which is equivalent to `$cart->items()->where($where)->first()->delete()` 118 | 119 | #### Clear Cart Items: `$cart->clear()` 120 | 121 | Remove all items from the cart 122 | 123 | ```php 124 | $cart->clear(); 125 | ``` 126 | #### Checkout cart: `$cart->checkout()` 127 | 128 | This method only updates `status` and `placed_at` column values. `status` is set to `pending` 129 | 130 | ```php 131 | $cart->checkout(); 132 | 133 | ``` 134 | 135 | 136 | #### Move item(s) between carts: 137 | 138 | To move all items from one cart to another cart instance: 139 | 140 | ```php 141 | $cart = app('cart'); 142 | $wishlist = app('cart', ['name' => 'wishlist']); 143 | 144 | //move all wishlist items to cart 145 | $wishlist->moveItemsTo($cart); 146 | ``` 147 | 148 | To move a single item between carts: 149 | 150 | ```php 151 | $cart = app('cart'); 152 | $wishlist = app('cart', ['name' => 'wishlist']); 153 | 154 | //move an wishlist item to cart 155 | $item = $wishlist->items()->where(['product_id' => 1])->first(); 156 | $item->moveTo($cart); 157 | 158 | ``` 159 | 160 | 161 | #### Get Cart Attributes 162 | 163 | ```php 164 | $total_price = $cart->total_price; // cart total price 165 | $item_count = $cart->item_count; // cart items count 166 | $date_placed = $cart->placed_at; // returns Carbon instance 167 | ``` 168 | #### Cart Statuses 169 | 170 | Supports several cart statuses: 171 | * `active`: currently adding items to the cart 172 | * `expired`: cart is expired, meaningful for session carts 173 | * `pending`: checked out carts 174 | * `completed`: completed carts 175 | 176 | ```php 177 | use NabeelAlalmai\DBcart\Models\Cart; 178 | 179 | // get carts based on their status: active/expired/pending/complete 180 | $active_carts = Cart::active()->get(); 181 | $expired_carts = Cart::expired()->get(); 182 | $pending_carts = Cart::pending()->get(); 183 | $completed_carts = Cart::completed()->get(); 184 | ``` 185 | #### Working with Multiple Cart Instances 186 | 187 | By default, cart instances are named as `default`. You can load other instances by providing a name: 188 | 189 | ```php 190 | $cart = app('cart'); // default cart, same as: app('cart', [ 'name' => 'default']; 191 | $sales_cart = app('cart', [ 'name' => 'sales']); 192 | $wishlist = app('cart', [ 'name' => 'wishlist']); 193 | ``` 194 | or, without singleton carts: 195 | 196 | ```php 197 | use NabeelAlalmai\DBcart\Models\Cart; 198 | //... 199 | $cart = Cart::current(); 200 | $sales_cart = Cart::current('sales'); 201 | $wishlist = Cart::current('wishlist'); 202 | ``` 203 | 204 | To get carts other than `default`: 205 | 206 | ```php 207 | $pending_sales_carts = Cart::instance('sales')->pending()->get(); 208 | ``` 209 | 210 | #### Delete Expired Carts: 211 | 212 | The guest carts depend on the session lifetime. They are valid as long as the session is not expired. You can increase session lifetime in `config/session.php` to increase cart lifetime. When a session expires, a new cart instance will be created in database and the old one will no longer be used. Over time, these expired carts could pile-up in database. 213 | 214 | Laravel Task Scheduler comes to the rescue. To enable scheduler just add following to the crontab in your server: 215 | 216 | * * * * * php /path/to/artisan schedule:run >> /dev/null 2>&1 217 | 218 | That's it. The module will now check for expired carts in every hour and delete them. This won't affect the carts for loggedin users. 219 | 220 | #### Other features: 221 | 222 | Get Cart User: `$cart->user` 223 | 224 | Get Item Product: `$item->product` 225 | 226 | Is Cart Empty: `$cart->isEmpty()` 227 | 228 | If an item exists in cart: `$cart->hasItem(['id' => 10])` 229 | 230 | Expire the cart: `cart->expire();` 231 | 232 | Set to `completed` status: `$cart->complete();` 233 | 234 | 235 | ## Extending Cart Model 236 | It's easy to extend DBCart. You can extend base DBCart model and add your own methods or columns. Follow these steps to extend the cart model: 237 | 238 | 1. Create a model by extending `NabeelAlalmai\DBcart\Models\Cart`: 239 | ```php 240 | namespace App; 241 | 242 | use NabeelAlalmai\DBcart\Models\Cart as BaseCart; 243 | 244 | class Cart extends BaseCart 245 | { 246 | //override or add your methods here ... 247 | 248 | public function getSubTotalAttribute(){ 249 | return $this->attributes['total_price']; 250 | } 251 | public function getGrandTotalAttribute(){ 252 | //taking discount, tax etc. into account 253 | return $this->sub_total - $this->discount; 254 | } 255 | 256 | } 257 | ``` 258 | 2. Update `cart_model` in `config/cart.php` with the fully qualified class name of the extended model. 259 | 260 | ```php 261 | 'cart_model' => App\Cart::class, 262 | ``` 263 | 3. That's it, you can now load the cart as usual: 264 | 265 | ```php 266 | $cart = App::make('cart'); 267 | ``` 268 | 269 | You can also follow the above steps and create your own `CartLine` model by extending `NabeelAlalmai\DBcart\Models\CartLine`. Be sure to update `config/cart.php` to reflect your changes. 270 | 271 | ## Disclaimer 272 | I was using `dbcart by Hassansin` but it doesn't support Laravel 6. So, I re-wrote the package to support Laravel > 5.5. If you use dbcart by Hassansin you can use this package and it should work with no conflicts. 273 | 274 | ## Support 275 | 276 | Reach out to me at one of the following places! 277 | 278 | - Twitter at `Nabeel Al Almai - نبيل الالمعي` 279 | 280 | --- 281 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nabeelalalmai/dbcart-laravel", 3 | "description": "Shopping Cart for Laravel > 5.5 , Laravel 6 and Laravel 7 that uses Database instead of Sessions.", 4 | "keywords": [ 5 | "Nabeel Al-Almai", 6 | "Shopping Cart", 7 | "dbcart-laravel", 8 | "database", 9 | "e-commerce", 10 | "laravel" 11 | ], 12 | "homepage": "https://github.com/nabeel-alalmai/dbcart-laravel", 13 | "license": "MIT", 14 | "authors": [ 15 | { 16 | "name": "Nabeel Al-Almai", 17 | "email": "nabeel.alalmai@gmail.com" 18 | } 19 | ], 20 | 21 | "require": { 22 | "php": "^7.2|^8.0", 23 | "illuminate/support": "~5.5.0|~5.6.0|~5.7.0|~5.8.0|^6.0|^7.0" 24 | }, 25 | "require-dev": { 26 | "fzaninotto/faker": "~1.4", 27 | "mockery/mockery": "^1.2.3", 28 | "orchestra/testbench": "^4.0" 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "NabeelAlalmai\\DBcart\\": "src/" 33 | } 34 | }, 35 | "extra": { 36 | "laravel": { 37 | "providers": [ 38 | "NabeelAlalmai\\DBcart\\CartServiceProvider" 39 | ] 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./tests 14 | 15 | 16 | 17 | 18 | ./src 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/CartServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes([ 25 | __DIR__ . '/config/cart.php' => config_path('cart.php'), 26 | ], 'config'); 27 | 28 | $this->publishes([ 29 | __DIR__ . '/database/migrations/' => database_path('migrations') 30 | ], 'migrations'); 31 | 32 | $this->app->booted(function () { 33 | $schedule = $this->app->make(Schedule::class); 34 | $schedule_frequency = config('cart.schedule_frequency', 'hourly'); 35 | $schedule->command('cart:cleanup')->$schedule_frequency(); 36 | 37 | }); 38 | if ($this->app->runningInConsole()) { 39 | $this->commands([ 40 | Console\Commands\CartCleanup::class 41 | ]); 42 | } 43 | } 44 | 45 | /** 46 | * Register the service provider. 47 | * 48 | * @return void 49 | */ 50 | public function register() 51 | { 52 | $this->mergeConfigFrom( 53 | __DIR__ . '/config/cart.php', 'cart' 54 | ); 55 | 56 | $this->app['cart_instances'] = []; 57 | 58 | $this->app->bind('cart', function ($app, $params) { 59 | $instance_name = !empty($params['name']) ? $params['name'] : 'default'; 60 | $cart_instances = $app['cart_instances']; 61 | 62 | //implement singleton carts 63 | if (empty($cart_instances[$instance_name])) { 64 | $model = config('cart.cart_model'); 65 | $cart_instances[$instance_name] = $model::current($instance_name); 66 | $app['cart_instances'] = $cart_instances; 67 | } 68 | return $app['cart_instances'][$instance_name]; 69 | }); 70 | 71 | } 72 | 73 | 74 | /** 75 | * Get the services provided by the provider. 76 | * 77 | * @codeCoverageIgnore 78 | * @return array 79 | */ 80 | public function provides() 81 | { 82 | return ['cart', 'cart_instances']; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Console/Commands/CartCleanup.php: -------------------------------------------------------------------------------- 1 | subMinutes($session_lifetime); 47 | $cart_model::where('updated_at', '<', $date) 48 | ->where('session', '!=', '') 49 | ->where('status','active') 50 | ->update(['status' => 'expired']); 51 | } 52 | 53 | //delete expired carts 54 | if(config('cart.delete_expired', false)){ 55 | $cart_model = config('cart.cart_model'); 56 | $cart_line_model = config('cart.cart_line_model'); 57 | $cart_ids = $cart_model::where('status','expired')->pluck('id'); 58 | 59 | $cart_line_model::whereIn('cart_id', $cart_ids->toArray())->delete(); 60 | $cart_model::where('status','expired')->delete(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Facades/Cart.php: -------------------------------------------------------------------------------- 1 | 'float', 46 | 'item_count' => 'int', 47 | ]; 48 | 49 | /** 50 | * Get the current cart instance 51 | * 52 | * @param string $instance_name 53 | * @param null $save_on_demand 54 | * @return mixed 55 | */ 56 | public static function current($instance_name = 'default', $save_on_demand = null) 57 | { 58 | $save_on_demand = is_null($save_on_demand) ? config('cart.save_on_demand', false) : $save_on_demand; 59 | return static::init($instance_name, $save_on_demand); 60 | } 61 | 62 | /** 63 | * Initialize the cart 64 | * 65 | * @param string $instance_name 66 | * @param $save_on_demand 67 | * @return mixed 68 | */ 69 | public static function init($instance_name, $save_on_demand) 70 | { 71 | $request = app('request'); 72 | $session_id = $request->session()->getId(); 73 | $user_id = $request->user()->id ?? false; 74 | $app = Application::getInstance(); 75 | $carts = $request->offsetGet("cart_instances"); 76 | 77 | //if user logged in 78 | if ($user_id) { 79 | $user_cart = static::active()->user($user_id)->where(['name' => $instance_name])->first(); 80 | 81 | $session_cart_id = $request->session()->get('cart_' . $instance_name); 82 | $session_cart = is_null($session_cart_id) ? null : static::active()->session($session_cart_id)->where('name', $instance_name)->first(); 83 | 84 | switch (true) { 85 | 86 | case is_null($user_cart) && is_null($session_cart): //no user cart or session cart 87 | $attributes = array( 88 | 'user_id' => $user_id, 89 | 'name' => $instance_name, 90 | 'status' => STATUS_ACTIVE 91 | ); 92 | if ($save_on_demand) 93 | $cart = new static($attributes); 94 | else 95 | $cart = static::create($attributes); 96 | 97 | break; 98 | 99 | case !is_null($user_cart) && is_null($session_cart): //only user cart 100 | $cart = $user_cart; 101 | break; 102 | 103 | case is_null($user_cart) && !is_null($session_cart): //only session cart 104 | $cart = $session_cart; 105 | $cart->user_id = $user_id; 106 | $cart->session = null; 107 | $cart->save(); 108 | break; 109 | 110 | case !is_null($user_cart) && !is_null($session_cart): //both user cart and session cart exists 111 | $session_cart->moveItemsTo($user_cart); //move items from session cart to user cart 112 | $session_cart->delete(); //delete session cart 113 | $cart = $user_cart; 114 | break; 115 | } 116 | 117 | $request->session()->forget('cart_' . $instance_name); //no longer need it. 118 | $carts[$instance_name] = $cart; 119 | } //guest user, create cart with session id 120 | else { 121 | $attributes = array( 122 | 'session' => $session_id, 123 | 'name' => $instance_name, 124 | 'status' => STATUS_ACTIVE 125 | ); 126 | $cart = static::firstOrNew($attributes); 127 | 128 | if (!$save_on_demand) 129 | $cart->save(); 130 | 131 | //save current session id, since upon login session id will be regenerated 132 | //we will use this id to get back the cart before login 133 | $request->session()->put('cart_' . $instance_name, $session_id); 134 | $carts[$instance_name] = $cart; 135 | } 136 | 137 | 138 | $app->offsetSet("cart_instances", $carts); 139 | 140 | return $carts[$instance_name]; 141 | } 142 | 143 | /** 144 | * The "booting" method of the model. 145 | * 146 | * @return void 147 | */ 148 | protected static function boot() 149 | { 150 | parent::boot(); 151 | 152 | //delete line items 153 | static::deleting(function (Cart $cart) { 154 | $cart->items()->delete(); 155 | }); 156 | } 157 | 158 | /** 159 | * Get the items for the cart. 160 | */ 161 | public function items() 162 | { 163 | return $this->hasMany(config('cart.cart_line_model')); 164 | } 165 | 166 | /** 167 | * Get the user that owns the cart. 168 | * @codeCoverageIgnore 169 | */ 170 | public function user() 171 | { 172 | return $this->belongsTo(config('cart.user_model')); 173 | } 174 | 175 | public function scopePending($query) 176 | { 177 | return $query->where('status', STATUS_PENDING); 178 | } 179 | 180 | public function scopeCompleted($query) 181 | { 182 | return $query->where('status', STATUS_COMPLETE); 183 | } 184 | 185 | public function scopeExpired($query) 186 | { 187 | return $query->where('status', STATUS_EXPIRED); 188 | } 189 | 190 | public function scopeActive($query) 191 | { 192 | return $query->where('status', STATUS_ACTIVE); 193 | } 194 | 195 | public function scopeInstance($query, $instance_name = 'default') 196 | { 197 | return $query->where('name', $instance_name); 198 | } 199 | 200 | public function scopeUser($query, $user_id = null) 201 | { 202 | return $query->where('user_id', $user_id); 203 | } 204 | 205 | public function scopeSession($query, $session_id = null) 206 | { 207 | $session_id = $session_id ?: app('request')->session()->getId(); 208 | return $query->where('session', $session_id); 209 | } 210 | 211 | public function setTotalPriceAttribute($value) 212 | { 213 | $this->attributes['total_price'] = $value; 214 | } 215 | 216 | /** 217 | * Add item to a cart. Increases quantity if the item already exists. 218 | * 219 | * @param array $attributes 220 | * @return Model|\Illuminate\Database\Eloquent\Relations\HasMany|object|null 221 | */ 222 | public function addItem(array $attributes = []) 223 | { 224 | if ($item = $this->getItem(collect($attributes)->except(['quantity']))) { 225 | $item->quantity += $attributes['quantity']; 226 | $item->save(); 227 | return $item; 228 | } 229 | return $this->items()->create($attributes); 230 | } 231 | 232 | public function getItem($where) 233 | { 234 | if ($where instanceof Collection) { 235 | $where = $where->toArray(); 236 | } 237 | return $this->items()->where($where)->first(); 238 | } 239 | 240 | /** 241 | * remove item from a cart 242 | * 243 | * @param array $attributes 244 | * @return bool|mixed|null 245 | */ 246 | public function removeItem(array $attributes = []) 247 | { 248 | return $this->items()->where($attributes)->first()->delete(); 249 | } 250 | 251 | /** 252 | * update item in a cart 253 | * 254 | * @param array $where 255 | * @param array $values 256 | * @return bool|int 257 | */ 258 | public function updateItem(array $where, array $values) 259 | { 260 | return $this->items()->where($where)->first()->update($values); 261 | } 262 | 263 | /** 264 | * Cart checkout. 265 | * 266 | */ 267 | public function checkout() 268 | { 269 | return $this->update(['status' => STATUS_PENDING, 'placed_at' => Carbon::now()]); 270 | } 271 | 272 | /** 273 | * Expires a cart 274 | * 275 | */ 276 | public function expire() 277 | { 278 | return $this->update(['status' => STATUS_EXPIRED]); 279 | } 280 | 281 | /** 282 | * Set a cart as complete 283 | * 284 | */ 285 | public function complete() 286 | { 287 | return $this->update(['status' => STATUS_COMPLETE, 'completed_at' => Carbon::now()]); 288 | } 289 | 290 | /** 291 | * Check if cart is empty 292 | * 293 | */ 294 | public function isEmpty() 295 | { 296 | return empty($this->item_count); 297 | } 298 | 299 | public function hasItem($where) 300 | { 301 | return !is_null($this->getItem($where)); 302 | } 303 | 304 | /** 305 | * Empties a cart 306 | * 307 | */ 308 | public function clear() 309 | { 310 | $this->items()->delete(); 311 | $this->updateTimestamps(); 312 | $this->total_price = 0; 313 | $this->item_count = 0; 314 | $this->relations = []; 315 | return $this->save(); 316 | } 317 | 318 | /** 319 | * Move Items to another cart instance 320 | * 321 | * @param Cart $cart 322 | * @return Cart 323 | */ 324 | public function moveItemsTo(Cart $cart) 325 | { 326 | 327 | DB::transaction(function () use (&$cart) { 328 | $current_items = $cart->items()->pluck('product_id'); 329 | $items_to_move = $this->items()->whereNotIn('product_id', $current_items->toArray())->get(); 330 | if ($items_to_move->count() === 0) { 331 | return; 332 | } 333 | $this->items()->whereNotIn('product_id', $current_items->toArray())->update([ 334 | 'cart_id' => $cart->id 335 | ]); 336 | foreach ($items_to_move as $item) { 337 | $this->item_count -= $item->quantity; 338 | $this->total_price -= $item->getPrice(); 339 | $cart->item_count += $item->quantity; 340 | $cart->total_price += $item->getPrice(); 341 | } 342 | $this->relations = []; 343 | $cart->relations = []; 344 | $this->save(); 345 | $cart->save(); 346 | }); 347 | return $cart; 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /src/Models/CartLine.php: -------------------------------------------------------------------------------- 1 | 'int', 38 | 'product_id' => 'int', 39 | 'quantity' => 'int', 40 | 'unit_price' => 'float', 41 | ]; 42 | 43 | /** 44 | * Get the product record associated with the item. 45 | * @codeCoverageIgnore 46 | */ 47 | public function product() 48 | { 49 | return $this->belongsTo(config('cart.product_model'), 'product_id'); 50 | } 51 | 52 | /** 53 | * Get the cart that owns the item. 54 | */ 55 | public function cart() 56 | { 57 | return $this->belongsTo(config('cart.cart_model'), 'cart_id'); 58 | } 59 | 60 | /* 61 | * Get Item orginal quantity before update 62 | * 63 | * @return integer 64 | */ 65 | 66 | public function getOriginalQuantity(){ 67 | return $this->original['quantity']; 68 | } 69 | 70 | /* 71 | * Get Item original price before update 72 | * 73 | * @return float 74 | */ 75 | public function getOriginalUnitPrice(){ 76 | return $this->original['unit_price']; 77 | } 78 | 79 | /* 80 | * Get Item price 81 | * 82 | * @return float 83 | */ 84 | public function getPrice(){ 85 | return $this->quantity * $this->unit_price; 86 | } 87 | 88 | /* 89 | * Get Item original price before update 90 | * 91 | * @return integer 92 | */ 93 | public function getOriginalPrice(){ 94 | return $this->getOriginalQuantity() * $this->getOriginalUnitPrice(); 95 | } 96 | 97 | /* 98 | * Get the singleton cart of this line item. 99 | * 100 | */ 101 | public function getCartInstance(){ 102 | $carts = app('cart_instances'); 103 | foreach ($carts as $name => $cart) { 104 | if($cart->id === $this->cart_id){ 105 | return $cart; 106 | } 107 | } 108 | return null; 109 | } 110 | 111 | /* 112 | * Move this item to another cart 113 | * 114 | * @param Cart $cart 115 | */ 116 | public function moveTo(Cart $cart){ 117 | $model = null; 118 | \DB::transaction(function () use($cart, &$model) { 119 | $this->delete(); // delete from own cart 120 | $attr = $this->attributes; 121 | unset($attr['cart_id']); 122 | $model = $cart->items()->create($attr); 123 | }); 124 | return $model; 125 | } 126 | 127 | /** 128 | * The "booting" method of the model. 129 | * 130 | * @return void 131 | */ 132 | protected static function boot() { 133 | parent::boot(); 134 | 135 | //when an item is created 136 | static::created(function(CartLine $line){ 137 | $cart = $line->getCartInstance() ?: $line->cart; 138 | $cart->total_price = $cart->total_price + $line->getPrice(); 139 | $cart->item_count = $cart->item_count + $line->quantity; 140 | $cart->relations = []; 141 | $cart->save(); 142 | }); 143 | 144 | //when an item is updated 145 | static::updated(function(CartLine $line){ 146 | $cart = $line->getCartInstance() ?: $line->cart; 147 | $cart->total_price = $cart->total_price - $line->getOriginalPrice() + $line->getPrice(); 148 | $cart->item_count = $cart->item_count - $line->getOriginalQuantity() + $line->quantity; 149 | $cart->relations = []; 150 | $cart->save(); 151 | }); 152 | 153 | static::deleted(function(CartLine $line){ 154 | $cart = $line->getCartInstance() ?: $line->cart; 155 | $cart->total_price = $cart->total_price - $line->getPrice(); 156 | $cart->item_count = $cart->item_count - $line->quantity; 157 | $cart->relations = []; 158 | $cart->save(); 159 | }); 160 | } 161 | } 162 | 163 | -------------------------------------------------------------------------------- /src/config/cart.php: -------------------------------------------------------------------------------- 1 | NabeelAlalmai\DBcart\Models\Cart::class, 17 | 18 | 19 | /* 20 | |-------------------------------------------------------------------------- 21 | | Cart Line Model 22 | |-------------------------------------------------------------------------- 23 | | 24 | | Cart Line Model to associate with Cart orders 25 | | 26 | */ 27 | 28 | 'cart_line_model' => NabeelAlalmai\DBcart\Models\CartLine::class, 29 | 30 | /* 31 | |-------------------------------------------------------------------------- 32 | | User Model 33 | |-------------------------------------------------------------------------- 34 | | 35 | | User Model to associate with Cart orders 36 | | 37 | */ 38 | 39 | 'user_model' => App\User::class, 40 | 41 | /* 42 | |-------------------------------------------------------------------------- 43 | | Product Model 44 | |-------------------------------------------------------------------------- 45 | | 46 | | Product Model to associate with Cart Lines 47 | | 48 | */ 49 | 50 | 'product_model' => App\Product::class, 51 | 52 | 53 | /* 54 | |-------------------------------------------------------------------------- 55 | | Save on demand 56 | |-------------------------------------------------------------------------- 57 | | 58 | | When set to true, if a cart doesn't exist on database you'll be given a 59 | | new instance of cart object. You have to manually save the cart to DB by 60 | | $cart->save() . Useful if you want to avoid creating unnecessary empty carts 61 | | in database. But be sure to save cart before adding items 62 | | 63 | */ 64 | 65 | 'save_on_demand' => false, 66 | 67 | /* 68 | |-------------------------------------------------------------------------- 69 | | Auto Expire a cart 70 | |-------------------------------------------------------------------------- 71 | | 72 | | Enable or disable auto expire a cart. Only applicable for session carts. 73 | | When set to true, cart status will be set to 'expired' after session 'lifetime' 74 | | 75 | | Needs Laravel task scheduler to be started: http://laravel.com/docs/master/scheduling 76 | */ 77 | 78 | 'expire_cart' => true, 79 | 80 | /* 81 | |-------------------------------------------------------------------------- 82 | | Delete Expired Cart 83 | |-------------------------------------------------------------------------- 84 | | 85 | | Delete expired carts. If set to true, all expired carts will be deleted. 86 | | 87 | | Needs Laravel task scheduler to be started: http://laravel.com/docs/master/scheduling 88 | */ 89 | 'delete_expired' => true, 90 | 91 | /* 92 | |-------------------------------------------------------------------------- 93 | | Schedule Frequency 94 | |-------------------------------------------------------------------------- 95 | | 96 | | How often the scheduled command would run 97 | | 98 | */ 99 | 'schedule_frequency' => 'hourly', 100 | 101 | ]; 102 | -------------------------------------------------------------------------------- /src/database/migrations/2019_12_31_151631_create_cart_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->unsignedBigInteger('user_id')->nullable(); 19 | $table->string('session')->nullable(); 20 | $table->string('name')->default('default'); 21 | $table->string('status', 20)->default('active'); 22 | $table->decimal('total_price')->default(0.00); 23 | $table->integer('item_count')->default(0); 24 | $table->timestamp('placed_at')->nullable(); 25 | $table->timestamp('completed_at')->nullable(); 26 | $table->timestamps(); 27 | 28 | $table->index('name'); 29 | $table->index(['user_id', 'status']); 30 | $table->index(['session','status']); 31 | }); 32 | } 33 | 34 | /** 35 | * Reverse the migrations. 36 | * 37 | * @return void 38 | */ 39 | public function down() 40 | { 41 | Schema::dropIfExists('cart'); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/database/migrations/2019_12_31_151654_create_cart_lines_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->unsignedBigInteger('cart_id'); 19 | $table->integer('product_id')->unsigned(); 20 | $table->integer('quantity')->unsigned(); 21 | $table->decimal('unit_price'); 22 | 23 | $table->timestamps(); 24 | $table->foreign('cart_id')->references('id') 25 | ->on('cart') 26 | ->onDelete('cascade'); 27 | }); 28 | } 29 | 30 | /** 31 | * Reverse the migrations. 32 | * 33 | * @return void 34 | */ 35 | public function down() 36 | { 37 | Schema::dropIfExists('cart_lines'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/CartTest.php: -------------------------------------------------------------------------------- 1 | loadLaravelMigrations(['--database' => 'testbench']); 18 | $this->loadMigrationsFrom([ 19 | '--database' => 'testbench', 20 | '--path' => realpath(__DIR__ . '/../src/database/migrations'), 21 | ]); 22 | } 23 | 24 | public function testCreateItem() 25 | { 26 | $cart = Cart::current(); 27 | DB::enableQueryLog(); 28 | $cart->addItem([ 29 | 'product_id' => 1, 30 | 'unit_price' => 10.5, 31 | 'quantity' => 2 32 | ]); 33 | $this->assertCount(3, DB::getQueryLog()); 34 | $this->assertEquals(2, $cart->item_count); 35 | $this->assertCount(1, $cart->items); 36 | $this->assertCount(1, $cart->items()->get()); 37 | $this->assertCount(1, $cart->items()->where('quantity', '=', 2)->get()); 38 | $this->assertCount(1, $cart->items()->where('unit_price', '>=', 1)->get()); 39 | } 40 | 41 | public function testCreateDuplicateItem() 42 | { 43 | $cart = Cart::current(); 44 | $cart->addItem([ 45 | 'product_id' => 1, 46 | 'unit_price' => 10.5, 47 | 'quantity' => 2 48 | ]); 49 | DB::enableQueryLog(); 50 | $cart->addItem([ 51 | 'product_id' => 1, 52 | 'unit_price' => 10.5, 53 | 'quantity' => 3 54 | ]); 55 | $this->assertCount(3, DB::getQueryLog()); 56 | $this->assertEquals(5, $cart->item_count); 57 | $this->assertEquals(52.5, $cart->total_price); 58 | $this->assertCount(1, $cart->items); 59 | $this->assertCount(1, $cart->items()->get()); 60 | $this->assertCount(1, $cart->items()->where('quantity', '=', 5)->get()); 61 | $this->assertCount(1, $cart->items()->where('unit_price', '>=', 1)->get()); 62 | } 63 | 64 | public function testRemoveItem() 65 | { 66 | $cart = Cart::current(); 67 | $cart->addItem([ 68 | 'product_id' => 1, 69 | 'unit_price' => 10.5, 70 | 'quantity' => 2 71 | ]); 72 | $this->assertEquals(2, $cart->item_count); 73 | $this->assertCount(1, $cart->items); 74 | $this->assertCount(1, $cart->items()->get()); 75 | $this->assertCount(1, $cart->items()->where('quantity', '=', 2)->get()); 76 | $this->assertCount(1, $cart->items()->where('unit_price', '>=', 1)->get()); 77 | $this->assertEquals(21, $cart->total_price); 78 | $this->assertFalse($cart->isEmpty()); 79 | 80 | DB::enableQueryLog(); 81 | $cart->removeItem(['product_id' => 1]); 82 | $this->assertCount(3, DB::getQueryLog()); 83 | $this->assertEquals(0, $cart->item_count); 84 | $this->assertCount(0, $cart->items()->get()); 85 | $this->assertCount(0, $cart->items); 86 | $this->assertTrue($cart->isEmpty()); 87 | } 88 | 89 | public function testUpdateItem() 90 | { 91 | $cart = Cart::current(); 92 | $cart->addItem([ 93 | 'product_id' => 1, 94 | 'unit_price' => 10.5, 95 | 'quantity' => 2 96 | ]); 97 | $this->assertEquals(2, $cart->item_count); 98 | $this->assertCount(1, $cart->items()->get()); 99 | $this->assertCount(1, $cart->items); 100 | $this->assertEquals(10.5, $cart->items->first()->unit_price); 101 | $this->assertCount(1, $cart->items()->where('quantity', '=', 2)->get()); 102 | $this->assertCount(1, $cart->items()->where('unit_price', '>=', 1)->get()); 103 | $this->assertEquals(21, $cart->total_price); 104 | 105 | DB::enableQueryLog(); 106 | $cart->updateItem(['product_id' => 1], ['unit_price' => 15.5, 'quantity' => 3]); 107 | $this->assertCount(3, DB::getQueryLog()); 108 | 109 | $this->assertCount(1, $cart->items()->get()); 110 | $this->assertCount(1, $cart->items); 111 | $this->assertEquals(15.5, $cart->items->first()->unit_price); 112 | $this->assertEquals(3, $cart->item_count); 113 | $this->assertEquals(46.5, $cart->total_price); 114 | } 115 | 116 | public function testMoveItemsToEmptyCart() 117 | { 118 | $cart1 = app('cart', ['name' => 'cart1']); 119 | $cart2 = app('cart', ['name' => 'cart2']); 120 | $cart1->addItem([ 121 | 'product_id' => 1, 122 | 'unit_price' => 10.5, 123 | 'quantity' => 2 124 | ]); 125 | $cart1->addItem([ 126 | 'product_id' => 2, 127 | 'unit_price' => 11.5, 128 | 'quantity' => 1 129 | ]); 130 | $this->assertFalse($cart1->isEmpty()); 131 | $this->assertTrue($cart2->isEmpty()); 132 | $this->assertCount(2, $cart1->items()->get()); 133 | $this->assertCount(0, $cart2->items()->get()); 134 | $this->assertEquals(3, $cart1->item_count); 135 | $this->assertEquals(32.5, $cart1->total_price); 136 | $this->assertEquals(0, $cart2->item_count); 137 | $this->assertEquals(0, $cart2->total_price); 138 | 139 | DB::enableQueryLog(); 140 | $cart1->moveItemsTo($cart2); 141 | $this->assertCount(5, DB::getQueryLog()); 142 | $this->assertTrue($cart1->isEmpty()); 143 | $this->assertFalse($cart2->isEmpty()); 144 | $this->assertCount(0, $cart1->items()->get()); 145 | $this->assertCount(2, $cart2->items()->get()); 146 | $this->assertEquals(0, $cart1->item_count); 147 | $this->assertEquals(0, $cart1->total_price); 148 | $this->assertEquals(3, $cart2->item_count); 149 | $this->assertEquals(32.5, $cart2->total_price); 150 | } 151 | 152 | public function testMoveItemsToNonEmptyCart() 153 | { 154 | $cart1 = app('cart', ['name' => 'cart1']); 155 | $cart2 = app('cart', ['name' => 'cart2']); 156 | $cart1->addItem([ 157 | 'product_id' => 1, 158 | 'unit_price' => 10.5, 159 | 'quantity' => 2 160 | ]); 161 | $cart1->addItem([ 162 | 'product_id' => 2, 163 | 'unit_price' => 11.5, 164 | 'quantity' => 1 165 | ]); 166 | 167 | $cart2->addItem([ 168 | 'product_id' => 3, 169 | 'unit_price' => 10, 170 | 'quantity' => 1 171 | ]); 172 | 173 | $this->assertFalse($cart1->isEmpty()); 174 | $this->assertFalse($cart2->isEmpty()); 175 | $this->assertCount(2, $cart1->items()->get()); 176 | $this->assertCount(1, $cart2->items()->get()); 177 | $this->assertEquals(3, $cart1->item_count); 178 | $this->assertEquals(32.5, $cart1->total_price); 179 | $this->assertEquals(1, $cart2->item_count); 180 | $this->assertEquals(10, $cart2->total_price); 181 | 182 | DB::enableQueryLog(); 183 | $cart1->moveItemsTo($cart2); 184 | $this->assertCount(5, DB::getQueryLog()); 185 | $this->assertTrue($cart1->isEmpty()); 186 | $this->assertTrue($cart1->isEmpty()); 187 | $this->assertFalse($cart2->isEmpty()); 188 | $this->assertCount(0, $cart1->items()->get()); 189 | $this->assertCount(3, $cart2->items()->get()); 190 | $this->assertEquals(0, $cart1->item_count); 191 | $this->assertEquals(0, $cart1->total_price); 192 | $this->assertEquals(4, $cart2->item_count); 193 | $this->assertEquals(42.5, $cart2->total_price); 194 | } 195 | 196 | public function testMoveItemsToNonEmptyCartWithDuplicateProduct() 197 | { 198 | $cart1 = app('cart', ['name' => 'cart1']); 199 | $cart2 = app('cart', ['name' => 'cart2']); 200 | $cart1->addItem([ 201 | 'product_id' => 1, 202 | 'unit_price' => 10.5, 203 | 'quantity' => 2 204 | ]); 205 | $cart1->addItem([ 206 | 'product_id' => 2, 207 | 'unit_price' => 11.5, 208 | 'quantity' => 1 209 | ]); 210 | 211 | $cart2->addItem([ 212 | 'product_id' => 1, 213 | 'unit_price' => 11.5, 214 | 'quantity' => 1 215 | ]); 216 | 217 | $this->assertFalse($cart1->isEmpty()); 218 | $this->assertFalse($cart2->isEmpty()); 219 | $this->assertCount(2, $cart1->items()->get()); 220 | $this->assertCount(1, $cart2->items()->get()); 221 | $this->assertEquals(3, $cart1->item_count); 222 | $this->assertEquals(32.5, $cart1->total_price); 223 | $this->assertEquals(1, $cart2->item_count); 224 | $this->assertEquals(11.5, $cart2->total_price); 225 | 226 | $cart1->moveItemsTo($cart2); 227 | $this->assertFalse($cart1->isEmpty()); 228 | $this->assertFalse($cart2->isEmpty()); 229 | $this->assertCount(1, $cart1->items()->get()); 230 | $this->assertCount(2, $cart2->items()->get()); 231 | $this->assertEquals(2, $cart1->item_count); 232 | $this->assertEquals(21, $cart1->total_price); 233 | $this->assertEquals(2, $cart2->item_count); 234 | $this->assertEquals(23, $cart2->total_price); 235 | } 236 | 237 | public function testMoveEmptyCart() 238 | { 239 | $cart1 = app('cart', ['name' => 'cart1']); 240 | $cart2 = app('cart', ['name' => 'cart2']); 241 | 242 | DB::enableQueryLog(); 243 | $cart1->moveItemsTo($cart2); 244 | $this->assertCount(2, DB::getQueryLog()); 245 | $this->assertTrue($cart1->isEmpty()); 246 | $this->assertTrue($cart2->isEmpty()); 247 | } 248 | 249 | public function testMoveSingleItemToEmptyCart() 250 | { 251 | $cart1 = app('cart', ['name' => 'cart1']); 252 | $cart2 = app('cart', ['name' => 'cart2']); 253 | $item = $cart1->addItem([ 254 | 'product_id' => 1, 255 | 'unit_price' => 10.5, 256 | 'quantity' => 2 257 | ]); 258 | $cart1->addItem([ 259 | 'product_id' => 2, 260 | 'unit_price' => 11.5, 261 | 'quantity' => 1 262 | ]); 263 | $this->assertFalse($cart1->isEmpty()); 264 | $this->assertTrue($cart2->isEmpty()); 265 | $this->assertCount(2, $cart1->items()->get()); 266 | $this->assertCount(0, $cart2->items()->get()); 267 | $this->assertEquals(3, $cart1->item_count); 268 | $this->assertEquals(32.5, $cart1->total_price); 269 | $this->assertEquals(0, $cart2->item_count); 270 | $this->assertEquals(0, $cart2->total_price); 271 | $this->assertEquals($item->cart->id, $cart1->id); 272 | 273 | $item = $item->moveTo($cart2); 274 | $this->assertFalse($cart1->isEmpty()); 275 | $this->assertFalse($cart2->isEmpty()); 276 | $this->assertCount(1, $cart1->items()->get()); 277 | $this->assertCount(1, $cart2->items()->get()); 278 | $this->assertEquals(1, $cart1->item_count); 279 | $this->assertEquals(11.5, $cart1->total_price); 280 | $this->assertEquals(2, $cart2->item_count); 281 | $this->assertEquals(21, $cart2->total_price); 282 | $this->assertEquals($item->cart->id, $cart2->id); 283 | } 284 | 285 | public function testIsEmpty() 286 | { 287 | $cart = Cart::current(); 288 | DB::enableQueryLog(); 289 | $this->assertTrue($cart->isEmpty()); 290 | $this->assertCount(0, DB::getQueryLog()); 291 | $cart->addItem([ 292 | 'product_id' => 1, 293 | 'unit_price' => 10.5, 294 | 'quantity' => 2 295 | ]); 296 | 297 | $this->assertFalse($cart->isEmpty()); 298 | } 299 | 300 | public function testHasItem() 301 | { 302 | $cart = Cart::current(); 303 | DB::enableQueryLog(); 304 | $this->assertFalse($cart->hasItem(['product_id' => 1])); 305 | $this->assertCount(1, DB::getQueryLog()); 306 | $cart->addItem([ 307 | 'product_id' => 1, 308 | 'unit_price' => 10.5, 309 | 'quantity' => 2 310 | ]); 311 | 312 | $this->assertTrue($cart->hasItem(['product_id' => 1])); 313 | } 314 | 315 | public function testUnknownAttribute() 316 | { 317 | $cart = Cart::current(); 318 | $cart->addItem([ 319 | 'foo' => 1, 320 | 'product_id' => 2, 321 | 'quantity' => 1, 322 | 'unit_price' => 0, 323 | ]); 324 | $this->assertFalse($cart->hasItem(['foo' => 1])); 325 | } 326 | 327 | public function testClear() 328 | { 329 | $cart = Cart::current(); 330 | $cart->addItem([ 331 | 'product_id' => 1, 332 | 'unit_price' => 10.5, 333 | 'quantity' => 2 334 | ]); 335 | 336 | DB::enableQueryLog(); 337 | $cart->clear(); 338 | $this->assertCount(2, DB::getQueryLog()); 339 | } 340 | 341 | public function testCartStatuses() 342 | { 343 | $cart = Cart::current(); 344 | $this->assertCount(1, Cart::active()->get()); 345 | $this->assertCount(0, Cart::expired()->get()); 346 | $this->assertCount(0, Cart::pending()->get()); 347 | $this->assertCount(0, Cart::completed()->get()); 348 | 349 | DB::enableQueryLog(); 350 | $cart->checkout(); 351 | $this->assertCount(1, DB::getQueryLog()); 352 | $this->assertCount(0, Cart::active()->get()); 353 | $this->assertCount(0, Cart::expired()->get()); 354 | $this->assertCount(1, Cart::pending()->get()); 355 | $this->assertCount(0, Cart::completed()->get()); 356 | 357 | $cart->complete(); 358 | $this->assertCount(0, Cart::active()->get()); 359 | $this->assertCount(0, Cart::expired()->get()); 360 | $this->assertCount(0, Cart::pending()->get()); 361 | $this->assertCount(1, Cart::completed()->get()); 362 | 363 | $cart->expire(); 364 | $this->assertCount(0, Cart::active()->get()); 365 | $this->assertCount(1, Cart::expired()->get()); 366 | $this->assertCount(0, Cart::pending()->get()); 367 | $this->assertCount(0, Cart::completed()->get()); 368 | } 369 | 370 | public function testInstance() 371 | { 372 | $cart = Cart::current("cart1"); 373 | $this->assertEquals($cart->id, Cart::instance("cart1")->first()->id); 374 | 375 | $cart = Cart::current("cart2"); 376 | $this->assertEquals($cart->id, Cart::instance("cart2")->first()->id); 377 | } 378 | 379 | public function testCartDelete() 380 | { 381 | $cart = Cart::current(); 382 | $cart->addItem([ 383 | 'product_id' => 1, 384 | 'unit_price' => 10.5, 385 | 'quantity' => 2 386 | ]); 387 | DB::enableQueryLog(); 388 | $cart->delete(); 389 | $this->assertCount(2, DB::getQueryLog()); 390 | } 391 | 392 | public function testFacade() 393 | { 394 | $cart = DBCart::current(); 395 | $this->assertEquals(1, $cart->id); 396 | } 397 | 398 | public function testCleanup() 399 | { 400 | $cart = DBCart::current(); 401 | $cart->addItem([ 402 | 'product_id' => 1, 403 | 'unit_price' => 10.5, 404 | 'quantity' => 2 405 | ]); 406 | $cart->expire(); 407 | 408 | $cmd = new CartCleanup(); 409 | DB::enableQueryLog(); 410 | $cmd->handle(); 411 | $this->assertCount(4, DB::getQueryLog()); 412 | } 413 | 414 | public function testScopeUser() 415 | { 416 | $cart = DBCart::current(); 417 | $cart->user_id = 100; 418 | $cart->save(); 419 | $this->assertEquals(null, Cart::active()->user()->first()); 420 | $this->assertEquals(null, Cart::active()->user(200)->first()); 421 | $this->assertEquals($cart->id, Cart::active()->user(100)->first()->id); 422 | } 423 | 424 | public function testSession() 425 | { 426 | $cart = DBCart::current(); 427 | $cart2 = DBCart::current('another'); 428 | $this->assertEquals($cart->id, Cart::session()->first()->id); 429 | $this->assertCount(2, Cart::session()->get()); 430 | } 431 | 432 | protected function getPackageProviders($app) 433 | { 434 | return [CartServiceProvider::class]; 435 | } 436 | 437 | protected function getEnvironmentSetUp($app) 438 | { 439 | // Setup default database to use sqlite :memory: 440 | $app['config']->set('cart.expire_cart', true); 441 | $app['config']->set('cart.delete_expired', true); 442 | $app['config']->set('database.default', 'testbench'); 443 | $app['config']->set('database.connections.testbench', [ 444 | 'driver' => 'sqlite', 445 | 'database' => ':memory:', 446 | 'prefix' => '', 447 | ]); 448 | $app['request']->setLaravelSession($app['session']->driver('array')); 449 | } 450 | 451 | protected function getPackageAliases($app) 452 | { 453 | return [ 454 | 'DBCart' => \NabeelAlalmai\DBcart\Facades\Cart::class 455 | ]; 456 | } 457 | } 458 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 |