commit 77fa8601b967536bc38b281ea9f5b11410ae0409 Author: Joel Male Date: Wed Aug 14 10:46:23 2024 +1000 init commit :rocket diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..80c7fdf --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,17 @@ +version: 2 + +updates: + - package-ecosystem: "github-actions" + directory: "/" + target-branch: "develop" + schedule: + interval: "weekly" + labels: + - "dependencies" + - package-ecosystem: "composer" + directory: "/" + target-branch: "develop" + schedule: + interval: "weekly" + labels: + - "dependencies" diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 0000000..27c23a4 --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yml @@ -0,0 +1,32 @@ +name: dependabot-auto-merge +on: pull_request_target + +permissions: + pull-requests: write + contents: write + +jobs: + dependabot: + runs-on: ubuntu-latest + if: ${{ github.actor == 'dependabot[bot]' }} + steps: + + - name: Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@v2.2.0 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + + - name: Auto-merge Dependabot PRs for semver-minor updates + if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor'}} + run: gh pr merge --auto --merge "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + + - name: Auto-merge Dependabot PRs for semver-patch updates + if: ${{steps.metadata.outputs.update-type == 'version-update:semver-patch'}} + run: gh pr merge --auto --merge "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/php-formatting.yml b/.github/workflows/php-formatting.yml new file mode 100644 index 0000000..f9ebbd5 --- /dev/null +++ b/.github/workflows/php-formatting.yml @@ -0,0 +1,25 @@ +name: PHP Formatting + +on: pull_request + +jobs: + php-formatting: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: "Run Pint" + uses: aglipanci/laravel-pint-action@latest + with: + verboseMode: true + configPath: "pint.json" + pintVersion: 1.17.2 + + - name: Commit changes + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "Apply Laravel Pint" + commit_options: '--no-verify --signoff' + file_pattern: "*.php" + commit_user_name: "Pixel Tech" + commit_user_email: "tech@wearepixel.com.au" + commit_author: "Pixel Tech" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..3ba70c4 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,40 @@ +name: Tests + +on: + push: + branches: [master, develop] + pull_request: + branches: [master, develop] + +jobs: + tests: + name: Tests + runs-on: ubuntu-20.04 + strategy: + fail-fast: true + matrix: + php: ['8.1','8.2', '8.3'] + stability: [prefer-stable] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, gd, redis, memcached + tools: composer:v2 + coverage: none + + - name: Install dependencies + uses: nick-fields/retry@v3 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress ${{ matrix.flags }} + + + - name: Execute tests + run: vendor/bin/pest \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6872d8b --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/vendor +.DS_Store +.idea +.phpunit.result.cache +READMETODO.md \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..9eb380e --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Pixel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..8217694 --- /dev/null +++ b/README.md @@ -0,0 +1,491 @@ +[![Latest Version on Packagist](https://img.shields.io/packagist/v/wearepixel/laravel-cart.svg?style=flat-square)](https://packagist.org/packages/wearepixel/laravel-cart) +[![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/wearepixel/laravel-cart/tests.yml?branch=master&label=Tests)](https://github.com/wearepixel/laravel-cart/actions?query=workflow%3ATests+branch%3Amaster) +[![Total Downloads](https://img.shields.io/packagist/dt/wearepixel/laravel-cart.svg?style=flat-square)](https://packagist.org/packages/wearepixel/laravel-cart) +[![License](https://poser.pugx.org/wearepixel/laravel-cart/license.svg)](https://packagist.org/packages/wearepixel/laravel-cart) + +A Cart Implementation for Laravel. + +Supported Laravel Versions: 9, 10, 11 + +## Quick Demo + +```php +// Add an item to the cart +\Cart::add(1, 'Product 1', 19.99, 2, ['size' => 'large']); + +// Get the cart contents +$cartContents = \Cart::getContent(); + +// Update item quantity by its id +\Cart::update(1, ['quantity' => 3]); + +// Remove an item from the cart by its id +\Cart::remove(1); + +// Get cart total +\$total = Cart::getTotal(); + +// Clear the entire cart +\Cart::clear(); +``` + +## Installing + +Install the package through [Composer](http://getcomposer.org/). + +`composer require "wearepixel/laravel-cart"` + +## Configuration + +You can publish the configuration file to customize various options. + +```php +php artisan vendor:publish --provider="Wearepixel\Cart\CartServiceProvider" --tag="config" +``` + +## HOW TO USE + +- [Usage](#usage) +- [Conditions](#conditions) +- [Items](#items) +- [Associating Models](#associating-models) +- [Instances](#instances) +- [Exceptions](#exceptions) +- [Events](#events) +- [Format Response](#format) +- [Examples](#examples) +- [Using Different Storage](#storage) +- [License](#license) + +## Usage + +The cart has a default sessionKey that holds the cart data and stores it in the session. + +This +also serves as a cart unique identifier which you can use to bind a cart to a specific user if you want to. + +Make sure to call `\Cart::session($sessionKey)` before calling any other cart methods. + +Usually this is not required. + +```php +// Binds the cart to a unique id (user id, session id, etc.) +\Cart::session(User::first()->id); + +// If you don't want to bind the cart on a project level, you can add it to each call +\Cart::session(User::first()->id)->add(1, 'Product 1', 19.99, 2, ['size' => 'large']); +``` + +### Adding to the cart: **Cart::add()** + +There are a few ways to add items to the cart. + +```php +// Add a simple product to the cart +Cart::add( + 455, # product id + 'Sample Item', # product name + 100.99, # product price + 2, # quantity + [] # optional attributes +); + +// array format +Cart::add([ + 456, # product id + 'Leather Shoes', # product name + 187, # product price + 1, # quantity + [] # optional attributes +]); + + +// add an item with attributes +Cart::add([ + 457, // product id + 'T-Shirt', // product name + 29.99, // product price + 1, // quantity + [ + 'size' => 'L', + 'color' => 'Blue' + ] // attributes +]); + +// add an item with conditions +Cart::add([ + 458, // product id + 'Headphones', // product name + 199.99, // product price + 1, // quantity + [], // attributes + [ + [ + 'name' => '10% Off', + 'type' => 'discount', + 'value' => '-10%' + ] + ] // conditions +]); + +// add multiple items at one time +Cart::add( + [ + 456, # product id + 'Leather Shoes', # product name + 187, # product price + 1, # quantity + [] # optional attributes + ], + [ + 431, # product id + 'Leather Jacket', # product name + 254.50, # product price + 1, # quantity + [] # optional attributes + ] +); +``` + +### Updating an item on a cart: **Cart::update()** + +```php +Cart::update( + 456, # product id + [ + 'name' => 'New Item Name', // new item name + 'price' => 98.67, // new item price as a float or string + ] +); + +// updating a product's quantity +Cart::update( + 456, # product id + [ + 'quantity' => 2, // by default adding the quantity (so if from 4 to 6) + ] +); + +// reducing it... +Cart::update( + 456, + [ + 'quantity' => -1, // so if from 4 to 3 + ] +); + +// you can replace the quantity by setting relative to false +Cart::update( + 456, # product id + [ + 'quantity' => [ + 'relative' => false, + 'value' => 5 // if the quantity was 2, it will now be 5 + ], + ] +); +``` + +### Removing an item on a cart: **Cart::remove()** + +```php +// Remove an item from the cart by its id +Cart::remove(456); +``` + +### Getting an item on a cart: **Cart::get()** + +```php +// Get an item from the cart by its id +Cart::get(456); + +// You can also get the total price of the item +$summedPrice = Cart::get($itemId)->getPriceSum(); +``` + +### Getting the cart content: **Cart::getContent()** + +```php +// Returns a collection of the cart's contents +$cartData = Cart::getContent(); + +// Gets the total number of items (not quantity) in the cart +$cartCollection->count(); + +// Transform the collection to an array or a JSON +$cartCollection->toArray(); +$cartCollection->toJson(); +``` + +Get cart total quantity: **Cart::getTotalQuantity()** + +```php +$cartTotalQuantity = Cart::getTotalQuantity(); +``` + +### Cart subtotal: **Cart::getSubTotal()** + +```php +$subTotal = Cart::getSubTotal(); +``` + +### Cart subtotal without conditions: **Cart::getSubTotalWithoutConditions()** + +```php +$subTotalWithoutConditions = Cart::getSubTotalWithoutConditions(); +``` + +Cart Total: **Cart::getTotal()** + +```php +$total = Cart::getTotal(); +``` + +### Check if cart is empty: **Cart::isEmpty()** + +```php +Cart::isEmpty(); +``` + +### Clearing the Cart: **Cart::clear()** + +```php +Cart::clear(); +``` + +## Conditions + +Conditions are very useful in terms for adding things like discounts, taxes, shipping costs, etc. + +Conditions can be added on the entire cart or on individual items. + +Conditions on a cart level should always have a `target` of `subtotal` or `total`. This tells the cart which value to apply the condition to. + +You can also provide an `order` to cart conditions which tells the cart in what order to apply the conditions. Item level conditions do not support the `order` parameter. + +### Conditions on the cart + +#### Adding a condition to the cart: **Cart::condition()** + +```php +// Add a single condition to the cart +$condition = new \Wearepixel\Cart\CartCondition([ + 'name' => 'Tax: 10%', + 'type' => 'tax', + 'target' => 'subtotal', // this condition will be applied to cart's subtotal when getSubTotal() is called. + 'value' => '10%', + 'attributes' => [ // add extra attributes here + 'description' => 'Compulsory tax', + ] +]); + +Cart::condition($condition); + +// Add multiple conditions +$tax = new \Wearepixel\Cart\CartCondition([ + 'name' => 'Tax: 10%', + 'type' => 'tax', + 'target' => 'subtotal', // this condition will be applied to cart's subtotal when getSubTotal() is called. + 'value' => '10%', + 'order' => 2 +]); + +$shipping = new \Wearepixel\Cart\CartCondition([ + 'name' => 'Shipping: $15', + 'type' => 'shipping', + 'target' => 'subtotal', // this condition will be applied to cart's subtotal when getSubTotal() is called. + 'value' => '+15', + 'order' => 1 +]); + +Cart::condition($tax); +Cart::condition($shipping); + +// or as an array +Cart::condition([$tax, $shipping]); + +// add condition to only apply on totals, not in subtotal +$shipping = new \Wearepixel\Cart\CartCondition([ + 'name' => 'Express Shipping $15', + 'type' => 'shipping', + 'target' => 'total', + 'value' => '+15', + 'order' => 1 +]); + +Cart::condition($shipping); +``` + +#### Getting conditions on the cart: **Cart::getConditions()** + +```php +// To get all applied conditions on a cart, use below: +$cartConditions = Cart::getConditions(); + +foreach($cartConditions as $condition) +{ + $condition->getTarget(); // the target of which the condition was applied + $condition->getName(); // the name of the condition + $condition->getType(); // the type + $condition->getValue(); // the value of the condition + $condition->getOrder(); // the order of the condition + $condition->getAttributes(); // the attributes of the condition, returns an empty [] if no attributes added +} + +// You can also get a condition that has been applied on the cart by using its name: +$condition = Cart::getCondition('Tax: 10%'); +$condition->getTarget(); // the target of which the condition was applied +$condition->getName(); // the name of the condition +$condition->getType(); // the type +$condition->getValue(); // the value of the condition +$condition->getAttributes(); // the attributes of the condition, returns an empty [] if no attributes added +``` + +### Calculating condition value: **Cart::getCondition('Tax: 10%')->getCalculatedValue($subTotal)** + +```php +$subTotal = Cart::getSubTotal(); +$condition = Cart::getCondition('VAT 12.5%'); +$conditionCalculatedValue = $condition->getCalculatedValue($subTotal); +``` + +### Condition on items + +Item conditions are useful if you have discounts to be applied specifically on an item and not on the whole cart value. + +```php + +// lets create first our condition instance +$saleCondition = new \Wearepixel\Cart\CartCondition([ + 'name' => '50% Off', + 'type' => 'tax', + 'value' => '-50%', +]); + +// Create the product data with the condition +$product = [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 100, + 'quantity' => 1, + 'attributes' => [], + 'conditions' => $saleCondition +]; + +// Now add the product to the cart +Cart::add($product); + +// You can of course also do multiple conditions on an item +$saleCondition = new \Wearepixel\Cart\CartCondition([ + 'name' => 'SALE 5%', + 'type' => 'sale', + 'value' => '-5%', +]); + +$discountCode = new CartCondition([ + 'name' => 'Discount Code', + 'type' => 'promo', + 'value' => '-25', +]); + +$item = [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 100, + 'quantity' => 1, + 'attributes' => [], + 'conditions' => [$saleCondition, $discountCode] +]; + +Cart::add($item); +``` + +> NOTE: All cart per-item conditions should be added before calling **Cart::getSubTotal()** + +Then Finally you can call **Cart::getSubTotal()** to get the Cart sub total with the applied conditions on each of the items. + +```php +// the subtotal will be calculated based on the conditions added that has target => "subtotal" +// and also conditions that are added on per item +$cartSubTotal = Cart::getSubTotal(); +``` + +#### Add condition to existing Item on the cart: **Cart::addItemCondition($productId, $itemCondition)** + +Adding Condition to an existing Item on the cart is simple as well. + +```php +$condition = new CartCondition([ + 'name' => 'COUPON 101', + 'type' => 'coupon', + 'value' => '-5%', +]); + +Cart::addItemCondition(456, $condition); +``` + +### Clearing Cart Conditions: **Cart::clearCartConditions()** + +```php +Cart::clearCartConditions() +``` + +### Remove a specific cart condtion: **Cart::removeCartCondition($conditionName)** + +```php +Cart::removeCartCondition('Summer Sale 5%') +``` + +### Remove a specific item condition: **Cart::removeItemCondition($itemId, $conditionName)** + +```php +Cart::removeItemCondition(456, 'SALE 5%') +``` + +### Clear all item conditions: **Cart::clearItemConditions($itemId)** + +```php +Cart::clearItemConditions(456) +``` + +### Get conditions by type: **Cart::getConditionsByType($type)** + +This returns all conditions that has been added to the cart by the type specified. + +```php +$tax = Cart::getConditionsByType('tax'); +``` + +### Remove conditions by type: **Cart::removeConditionsByType($type)** + +```php +Cart::removeConditionsByType('tax'); +``` + +## Cart Items + +The method **Cart::getContent()** returns a collection of items. + +Apart from the above methods, you can also get the price of an item with or without **item level conditions** applied. + +These methods do not apply cart level conditions. + +```php +// With no conditions, just the price * quantity +$item->getPriceSum(); + +// With conditions applied get the price of a single quantity +$item->getPriceWithConditions(); + +// Get the sum with conditions applied +$item->getPriceSumWithConditions(); + +// Without conditions applied +$item->getPriceSumWithConditions(); +``` + +## Credits + +Huge credits to the original maintainer of the package, located at [darryldecode/laravelshoppingcart](https://github.com/darryldecode/laravelshoppingcart) + +We have forked this package to update it to Laravel 11 and to add some features and support for newer PHP versions. \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..d42cc70 --- /dev/null +++ b/composer.json @@ -0,0 +1,53 @@ +{ + "name": "wearepixel/laravel-cart", + "version": "1.0.0", + "description": "A cart implementation for Laravel", + "keywords": [ + "laravel", + "shopping cart", + "cart" + ], + "license": "MIT", + "authors": [ + { + "name": "Darryl Fernandez", + "email": "engrdarrylfernandez@gmail.com" + }, + { + "name": "Joel Male", + "email": "joel@wearepixel.com.au" + } + ], + "require": { + "php": ">=8.0", + "illuminate/support": "^9.0|^10.0|^11.0", + "illuminate/validation": "^9.0|^10.0|^11.0", + "illuminate/translation": "^9.0|^10.0|^11.0" + }, + "require-dev": { + "mockery/mockery": "^1.4.2", + "laravel/pint": "dev-main", + "pestphp/pest": "2.34.4" + }, + "autoload": { + "psr-4": { + "Wearepixel\\Cart\\": "src/" + } + }, + "minimum-stability": "dev", + "extra": { + "laravel": { + "providers": [ + "Wearepixel\\Cart\\CartServiceProvider" + ], + "aliases": { + "Cart": "Wearepixel\\Cart\\Facades\\CartFacade" + } + } + }, + "config": { + "allow-plugins": { + "pestphp/pest-plugin": true + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..0fd89f9 --- /dev/null +++ b/composer.lock @@ -0,0 +1,5647 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "b25e260acd3e7cf87e600ba7103131f4", + "packages": [ + { + "name": "brick/math", + "version": "0.12.1", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "f510c0a40911935b77b86859eb5223d58d660df1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1", + "reference": "f510c0a40911935b77b86859eb5223d58d660df1", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^10.1", + "vimeo/psalm": "5.16.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "bignumber", + "brick", + "decimal", + "integer", + "math", + "mathematics", + "rational" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.12.1" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + } + ], + "time": "2023-11-29T23:19:16+00:00" + }, + { + "name": "carbonphp/carbon-doctrine-types", + "version": "dev-main", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", + "reference": "9098d1cc5ac1baf3c9733dc48f3388fbd35e7c9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/9098d1cc5ac1baf3c9733dc48f3388fbd35e7c9e", + "reference": "9098d1cc5ac1baf3c9733dc48f3388fbd35e7c9e", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "conflict": { + "doctrine/dbal": "<4.0.0 || >=5.0.0" + }, + "require-dev": { + "doctrine/dbal": "^4.0.0", + "nesbot/carbon": "^2.71.0 || ^3.0.0", + "phpunit/phpunit": "^10.3" + }, + "default-branch": true, + "type": "library", + "autoload": { + "psr-4": { + "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "KyleKatarn", + "email": "kylekatarnls@gmail.com" + } + ], + "description": "Types to use Carbon in Doctrine", + "keywords": [ + "carbon", + "date", + "datetime", + "doctrine", + "time" + ], + "support": { + "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/main" + }, + "funding": [ + { + "url": "https://github.com/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "time": "2024-04-07T15:25:35+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "f587d8c05c6e00f99cbfb32d565e4f6743c07ee4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/f587d8c05c6e00f99cbfb32d565e4f6743c07ee4", + "reference": "f587d8c05c6e00f99cbfb32d565e4f6743c07ee4", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^11.0", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^8.5 || ^9.5", + "vimeo/psalm": "^4.25 || ^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.1.x" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2024-02-18T21:47:00+00:00" + }, + { + "name": "doctrine/lexer", + "version": "3.1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "042e47e28c5e03f1cf6772fdf3dd4e0785433e05" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/042e47e28c5e03f1cf6772fdf3dd4e0785433e05", + "reference": "042e47e28c5e03f1cf6772fdf3dd4e0785433e05", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^5.21" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/3.1.x" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2024-07-29T08:29:21+00:00" + }, + { + "name": "egulias/email-validator", + "version": "4.x-dev", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "92c1ab0cc1d38e188f858cea12bbb90711fa759a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/92c1ab0cc1d38e188f858cea12bbb90711fa759a", + "reference": "92c1ab0cc1d38e188f858cea12bbb90711fa759a", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^2.0 || ^3.0", + "php": ">=8.1", + "symfony/polyfill-intl-idn": "^1.26" + }, + "require-dev": { + "phpunit/phpunit": "^10.2", + "vimeo/psalm": "^5.12" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" + }, + "default-branch": true, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Egulias\\EmailValidator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eduardo Gulias Davis" + } + ], + "description": "A library for validating emails against several RFCs", + "homepage": "https://github.com/egulias/EmailValidator", + "keywords": [ + "email", + "emailvalidation", + "emailvalidator", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/egulias/EmailValidator/issues", + "source": "https://github.com/egulias/EmailValidator/tree/4.x" + }, + "funding": [ + { + "url": "https://github.com/egulias", + "type": "github" + } + ], + "time": "2024-05-01T13:20:12+00:00" + }, + { + "name": "illuminate/collections", + "version": "11.x-dev", + "source": { + "type": "git", + "url": "https://github.com/illuminate/collections.git", + "reference": "dc68a7ccad93f3c2baa0bc8f559431c06391aa75" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/collections/zipball/dc68a7ccad93f3c2baa0bc8f559431c06391aa75", + "reference": "dc68a7ccad93f3c2baa0bc8f559431c06391aa75", + "shasum": "" + }, + "require": { + "illuminate/conditionable": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0", + "php": "^8.2" + }, + "suggest": { + "symfony/var-dumper": "Required to use the dump method (^7.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "11.x-dev" + } + }, + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Collections package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-08-01T18:54:27+00:00" + }, + { + "name": "illuminate/conditionable", + "version": "11.x-dev", + "source": { + "type": "git", + "url": "https://github.com/illuminate/conditionable.git", + "reference": "362dd761b9920367bca1427a902158225e9e3a23" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/conditionable/zipball/362dd761b9920367bca1427a902158225e9e3a23", + "reference": "362dd761b9920367bca1427a902158225e9e3a23", + "shasum": "" + }, + "require": { + "php": "^8.0.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Conditionable package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-06-28T20:10:30+00:00" + }, + { + "name": "illuminate/container", + "version": "11.x-dev", + "source": { + "type": "git", + "url": "https://github.com/illuminate/container.git", + "reference": "f47be671981a4438257c4fbfc3ad257f4e3e929a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/container/zipball/f47be671981a4438257c4fbfc3ad257f4e3e929a", + "reference": "f47be671981a4438257c4fbfc3ad257f4e3e929a", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^11.0", + "php": "^8.2", + "psr/container": "^1.1.1|^2.0.1" + }, + "provide": { + "psr/container-implementation": "1.1|2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Container\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Container package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-08-05T15:04:01+00:00" + }, + { + "name": "illuminate/contracts", + "version": "11.x-dev", + "source": { + "type": "git", + "url": "https://github.com/illuminate/contracts.git", + "reference": "34ead9385e0eab7e947807d77da66faf9bdf95ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/34ead9385e0eab7e947807d77da66faf9bdf95ff", + "reference": "34ead9385e0eab7e947807d77da66faf9bdf95ff", + "shasum": "" + }, + "require": { + "php": "^8.2", + "psr/container": "^1.1.1|^2.0.1", + "psr/simple-cache": "^1.0|^2.0|^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Contracts\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Contracts package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-08-01T19:08:33+00:00" + }, + { + "name": "illuminate/filesystem", + "version": "11.x-dev", + "source": { + "type": "git", + "url": "https://github.com/illuminate/filesystem.git", + "reference": "43b8d2af61dd6ca882e192fa4ca803d2294d4507" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/filesystem/zipball/43b8d2af61dd6ca882e192fa4ca803d2294d4507", + "reference": "43b8d2af61dd6ca882e192fa4ca803d2294d4507", + "shasum": "" + }, + "require": { + "illuminate/collections": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0", + "illuminate/support": "^11.0", + "php": "^8.2", + "symfony/finder": "^7.0" + }, + "suggest": { + "ext-fileinfo": "Required to use the Filesystem class.", + "ext-ftp": "Required to use the Flysystem FTP driver.", + "ext-hash": "Required to use the Filesystem class.", + "illuminate/http": "Required for handling uploaded files (^7.0).", + "league/flysystem": "Required to use the Flysystem local driver (^3.0.16).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.0).", + "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.0).", + "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.0).", + "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^7.0).", + "symfony/mime": "Required to enable support for guessing extensions (^7.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "11.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Illuminate\\Filesystem\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Filesystem package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-06-28T20:10:30+00:00" + }, + { + "name": "illuminate/macroable", + "version": "11.x-dev", + "source": { + "type": "git", + "url": "https://github.com/illuminate/macroable.git", + "reference": "e1cb9e51b9ed5d3c9bc1ab431d0a52fe42a990ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/macroable/zipball/e1cb9e51b9ed5d3c9bc1ab431d0a52fe42a990ed", + "reference": "e1cb9e51b9ed5d3c9bc1ab431d0a52fe42a990ed", + "shasum": "" + }, + "require": { + "php": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Macroable package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-06-28T20:10:30+00:00" + }, + { + "name": "illuminate/support", + "version": "11.x-dev", + "source": { + "type": "git", + "url": "https://github.com/illuminate/support.git", + "reference": "089c989beac69bd2633466d72d6ed07068b623ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/support/zipball/089c989beac69bd2633466d72d6ed07068b623ac", + "reference": "089c989beac69bd2633466d72d6ed07068b623ac", + "shasum": "" + }, + "require": { + "doctrine/inflector": "^2.0", + "ext-ctype": "*", + "ext-filter": "*", + "ext-mbstring": "*", + "illuminate/collections": "^11.0", + "illuminate/conditionable": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0", + "nesbot/carbon": "^2.72.2|^3.0", + "php": "^8.2", + "voku/portable-ascii": "^2.0" + }, + "conflict": { + "tightenco/collect": "<5.5.33" + }, + "replace": { + "spatie/once": "*" + }, + "suggest": { + "illuminate/filesystem": "Required to use the composer class (^11.0).", + "league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^2.0.2).", + "ramsey/uuid": "Required to use Str::uuid() (^4.7).", + "symfony/process": "Required to use the composer class (^7.0).", + "symfony/uid": "Required to use Str::ulid() (^7.0).", + "symfony/var-dumper": "Required to use the dd function (^7.0).", + "vlucas/phpdotenv": "Required to use the Env class and env helper (^5.4.1)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "11.x-dev" + } + }, + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Support package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-08-07T14:43:54+00:00" + }, + { + "name": "illuminate/translation", + "version": "11.x-dev", + "source": { + "type": "git", + "url": "https://github.com/illuminate/translation.git", + "reference": "3f416a9bc090ed7a9f6f6fff118e4953846bd185" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/translation/zipball/3f416a9bc090ed7a9f6f6fff118e4953846bd185", + "reference": "3f416a9bc090ed7a9f6f6fff118e4953846bd185", + "shasum": "" + }, + "require": { + "illuminate/collections": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/filesystem": "^11.0", + "illuminate/macroable": "^11.0", + "illuminate/support": "^11.0", + "php": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Translation\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Translation package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-08-01T18:54:27+00:00" + }, + { + "name": "illuminate/validation", + "version": "11.x-dev", + "source": { + "type": "git", + "url": "https://github.com/illuminate/validation.git", + "reference": "9398c6e24bf1b0ac12a7160f85bd0cc5e8f4305d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/validation/zipball/9398c6e24bf1b0ac12a7160f85bd0cc5e8f4305d", + "reference": "9398c6e24bf1b0ac12a7160f85bd0cc5e8f4305d", + "shasum": "" + }, + "require": { + "brick/math": "^0.9.3|^0.10.2|^0.11|^0.12", + "egulias/email-validator": "^3.2.5|^4.0", + "ext-filter": "*", + "ext-mbstring": "*", + "illuminate/collections": "^11.0", + "illuminate/container": "^11.0", + "illuminate/contracts": "^11.0", + "illuminate/macroable": "^11.0", + "illuminate/support": "^11.0", + "illuminate/translation": "^11.0", + "php": "^8.2", + "symfony/http-foundation": "^7.0", + "symfony/mime": "^7.0" + }, + "suggest": { + "illuminate/database": "Required to use the database presence verifier (^11.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Validation\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Validation package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-08-05T13:45:12+00:00" + }, + { + "name": "nesbot/carbon", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "cb4374784c87d0a0294e8513a52eb63c0aff3139" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/cb4374784c87d0a0294e8513a52eb63c0aff3139", + "reference": "cb4374784c87d0a0294e8513a52eb63c0aff3139", + "shasum": "" + }, + "require": { + "carbonphp/carbon-doctrine-types": "*", + "ext-json": "*", + "php": "^8.1", + "psr/clock": "^1.0", + "symfony/clock": "^6.3 || ^7.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation": "^4.4.18 || ^5.2.1|| ^6.0 || ^7.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "doctrine/dbal": "^3.6.3 || ^4.0", + "doctrine/orm": "^2.15.2 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.57.2", + "kylekatarnls/multi-tester": "^2.5.3", + "ondrejmirtes/better-reflection": "^6.25.0.4", + "phpmd/phpmd": "^2.15.0", + "phpstan/extension-installer": "^1.3.1", + "phpstan/phpstan": "^1.11.2", + "phpunit/phpunit": "^10.5.20", + "squizlabs/php_codesniffer": "^3.9.0" + }, + "default-branch": true, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev", + "dev-2.x": "2.x-dev" + }, + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "docs": "https://carbon.nesbot.com/docs", + "issues": "https://github.com/briannesbitt/Carbon/issues", + "source": "https://github.com/briannesbitt/Carbon" + }, + "funding": [ + { + "url": "https://github.com/sponsors/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon#sponsor", + "type": "opencollective" + }, + { + "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", + "type": "tidelift" + } + ], + "time": "2024-07-16T22:29:20+00:00" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/container", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "707984727bd5b2b670e59559d3ed2500240cf875" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/707984727bd5b2b670e59559d3ed2500240cf875", + "reference": "707984727bd5b2b670e59559d3ed2500240cf875", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "default-branch": true, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container" + }, + "time": "2023-09-22T11:11:30+00:00" + }, + { + "name": "psr/simple-cache", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "2d280c2aaa23a120f35d55cfde8581954a8e77fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/2d280c2aaa23a120f35d55cfde8581954a8e77fa", + "reference": "2d280c2aaa23a120f35d55cfde8581954a8e77fa", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "default-branch": true, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/master" + }, + "time": "2022-04-08T16:41:45+00:00" + }, + { + "name": "symfony/clock", + "version": "7.2.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/clock.git", + "reference": "738af07b16942450f71491c995d35996893c8d85" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/clock/zipball/738af07b16942450f71491c995d35996893c8d85", + "reference": "738af07b16942450f71491c995d35996893c8d85", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/clock": "^1.0", + "symfony/polyfill-php83": "^1.28" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/now.php" + ], + "psr-4": { + "Symfony\\Component\\Clock\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Decouples applications from the system clock", + "homepage": "https://symfony.com", + "keywords": [ + "clock", + "psr20", + "time" + ], + "support": { + "source": "https://github.com/symfony/clock/tree/7.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-08-06T10:13:52+00:00" + }, + { + "name": "symfony/finder", + "version": "7.2.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "51c0ff591e6295f21e1789e55a6e57457980895e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/51c0ff591e6295f21e1789e55a6e57457980895e", + "reference": "51c0ff591e6295f21e1789e55a6e57457980895e", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/7.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-08-12T10:26:02+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "7.2.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "4187b642a75b3d56ae1bf1461091eedbef2e06d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/4187b642a75b3d56ae1bf1461091eedbef2e06d6", + "reference": "4187b642a75b3d56ae1bf1461091eedbef2e06d6", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php83": "^1.27" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/cache": "<6.4" + }, + "require-dev": { + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/7.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-08-06T10:13:58+00:00" + }, + { + "name": "symfony/mime", + "version": "7.2.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "ab31998ee18185ce4cdea28bb52ffbda575378ee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/ab31998ee18185ce4cdea28bb52ffbda575378ee", + "reference": "ab31998ee18185ce4cdea28bb52ffbda575378ee", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/mailer": "<6.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows manipulating MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "support": { + "source": "https://github.com/symfony/mime/tree/7.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-08-12T12:27:03+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "a6e83bdeb3c84391d1dfe16f42e40727ce524a5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/a6e83bdeb3c84391d1dfe16f42e40727ce524a5c", + "reference": "a6e83bdeb3c84391d1dfe16f42e40727ce524a5c", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "symfony/polyfill-intl-normalizer": "^1.10", + "symfony/polyfill-php72": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "default-branch": true, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T15:07:36+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "default-branch": true, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T15:07:36+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "8740a072b86292957feb42703edde77fcfca84fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8740a072b86292957feb42703edde77fcfca84fb", + "reference": "8740a072b86292957feb42703edde77fcfca84fb", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "default-branch": true, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/1.x" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-06-20T08:18:00+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "e5670122fdf3e86a1f6dee9f043893e70c343b34" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/e5670122fdf3e86a1f6dee9f043893e70c343b34", + "reference": "e5670122fdf3e86a1f6dee9f043893e70c343b34", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "default-branch": true, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/1.x" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-06-20T08:18:00+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "2954fcffd79c04a3c5f50665aafb5f99c25a9d18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2954fcffd79c04a3c5f50665aafb5f99c25a9d18", + "reference": "2954fcffd79c04a3c5f50665aafb5f99c25a9d18", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "default-branch": true, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/1.x" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-06-20T08:18:00+00:00" + }, + { + "name": "symfony/translation", + "version": "7.2.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "a50ed69ace45368e096bc3981f456089253ed6cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/a50ed69ace45368e096bc3981f456089253ed6cb", + "reference": "a50ed69ace45368e096bc3981f456089253ed6cb", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^2.5|^3.0" + }, + "conflict": { + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<6.4", + "symfony/service-contracts": "<2.5", + "symfony/twig-bundle": "<6.4", + "symfony/yaml": "<6.4" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" + }, + "require-dev": { + "nikic/php-parser": "^4.18|^5.0", + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/7.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-08-05T08:22:14+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "dev-main", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", + "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "default-branch": true, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "voku/portable-ascii", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/voku/portable-ascii.git", + "reference": "b56450eed252f6801410d810c8e1727224ae0743" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b56450eed252f6801410d810c8e1727224ae0743", + "reference": "b56450eed252f6801410d810c8e1727224ae0743", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" + }, + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\": "src/voku/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Moelleken", + "homepage": "http://www.moelleken.org/" + } + ], + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", + "keywords": [ + "ascii", + "clean", + "php" + ], + "support": { + "issues": "https://github.com/voku/portable-ascii/issues", + "source": "https://github.com/voku/portable-ascii/tree/2.0.1" + }, + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://opencollective.com/portable-ascii", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "type": "tidelift" + } + ], + "time": "2022-03-08T17:03:00+00:00" + } + ], + "packages-dev": [ + { + "name": "brianium/paratest", + "version": "v7.4.3", + "source": { + "type": "git", + "url": "https://github.com/paratestphp/paratest.git", + "reference": "64fcfd0e28a6b8078a19dbf9127be2ee645b92ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/64fcfd0e28a6b8078a19dbf9127be2ee645b92ec", + "reference": "64fcfd0e28a6b8078a19dbf9127be2ee645b92ec", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-simplexml": "*", + "fidry/cpu-core-counter": "^1.1.0", + "jean85/pretty-package-versions": "^2.0.5", + "php": "~8.2.0 || ~8.3.0", + "phpunit/php-code-coverage": "^10.1.11 || ^11.0.0", + "phpunit/php-file-iterator": "^4.1.0 || ^5.0.0", + "phpunit/php-timer": "^6.0.0 || ^7.0.0", + "phpunit/phpunit": "^10.5.9 || ^11.0.3", + "sebastian/environment": "^6.0.1 || ^7.0.0", + "symfony/console": "^6.4.3 || ^7.0.3", + "symfony/process": "^6.4.3 || ^7.0.3" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0.0", + "ext-pcov": "*", + "ext-posix": "*", + "phpstan/phpstan": "^1.10.58", + "phpstan/phpstan-deprecation-rules": "^1.1.4", + "phpstan/phpstan-phpunit": "^1.3.15", + "phpstan/phpstan-strict-rules": "^1.5.2", + "squizlabs/php_codesniffer": "^3.9.0", + "symfony/filesystem": "^6.4.3 || ^7.0.3" + }, + "bin": [ + "bin/paratest", + "bin/paratest.bat", + "bin/paratest_for_phpstorm" + ], + "type": "library", + "autoload": { + "psr-4": { + "ParaTest\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Scaturro", + "email": "scaturrob@gmail.com", + "role": "Developer" + }, + { + "name": "Filippo Tessarotto", + "email": "zoeslam@gmail.com", + "role": "Developer" + } + ], + "description": "Parallel testing for PHP", + "homepage": "https://github.com/paratestphp/paratest", + "keywords": [ + "concurrent", + "parallel", + "phpunit", + "testing" + ], + "support": { + "issues": "https://github.com/paratestphp/paratest/issues", + "source": "https://github.com/paratestphp/paratest/tree/v7.4.3" + }, + "funding": [ + { + "url": "https://github.com/sponsors/Slamdunk", + "type": "github" + }, + { + "url": "https://paypal.me/filippotessarotto", + "type": "paypal" + } + ], + "time": "2024-02-20T07:24:02+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "phpstan/phpstan": "1.4.10 || 1.10.15", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psalm/plugin-phpunit": "0.18.4", + "psr/log": "^1 || ^2 || ^3", + "vimeo/psalm": "4.30.0 || 5.12.0" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "default-branch": true, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.3" + }, + "time": "2024-01-30T19:34:25+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/f92996c4d5c1a696a6a970e20f7c4216200fcc42", + "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^1.9.2", + "phpstan/phpstan-deprecation-rules": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.2.2", + "phpstan/phpstan-strict-rules": "^1.4.4", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.1.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2024-02-07T09:43:46+00:00" + }, + { + "name": "filp/whoops", + "version": "2.15.4", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a139776fa3f5985a50b509f2a02ff0f709d2a546", + "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.15.4" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2023-11-03T12:00:00+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "dd03e528e461e059e21cdd2ce50fdb842efe5a91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/dd03e528e461e059e21cdd2ce50fdb842efe5a91", + "reference": "dd03e528e461e059e21cdd2ce50fdb842efe5a91", + "shasum": "" + }, + "require": { + "php": "^5.3|^7.0|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "default-branch": true, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "hamcrest" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "support": { + "issues": "https://github.com/hamcrest/hamcrest-php/issues", + "source": "https://github.com/hamcrest/hamcrest-php/tree/master" + }, + "time": "2024-07-08T08:13:58+00:00" + }, + { + "name": "jean85/pretty-package-versions", + "version": "2.x-dev", + "source": { + "type": "git", + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "d2ed36bf14b8b68d7986272734cb6a54c7822554" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/d2ed36bf14b8b68d7986272734cb6a54c7822554", + "reference": "d2ed36bf14b8b68d7986272734cb6a54c7822554", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.0.0", + "php": "^7.1|^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "jean85/composer-provided-replaced-stub-package": "^1.0", + "phpstan/phpstan": "^1.4", + "phpunit/phpunit": "^7.5|^8.5|^9.6", + "vimeo/psalm": "^4.3 || ^5.0" + }, + "default-branch": true, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Jean85\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alessandro Lai", + "email": "alessandro.lai85@gmail.com" + } + ], + "description": "A library to get pretty versions strings of installed dependencies", + "keywords": [ + "composer", + "package", + "release", + "versions" + ], + "support": { + "issues": "https://github.com/Jean85/pretty-package-versions/issues", + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.x" + }, + "time": "2024-08-01T12:14:16+00:00" + }, + { + "name": "laravel/pint", + "version": "dev-main", + "source": { + "type": "git", + "url": "https://github.com/laravel/pint.git", + "reference": "e24a6aa83b23c0b64ab3486d68fbc88ab266e03f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/pint/zipball/e24a6aa83b23c0b64ab3486d68fbc88ab266e03f", + "reference": "e24a6aa83b23c0b64ab3486d68fbc88ab266e03f", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "ext-tokenizer": "*", + "ext-xml": "*", + "php": "^8.1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.61.1", + "illuminate/view": "^10.48.18", + "larastan/larastan": "^2.9.8", + "laravel-zero/framework": "^10.4.0", + "mockery/mockery": "^1.6.12", + "nunomaduro/termwind": "^1.15.1", + "pestphp/pest": "^2.35.0" + }, + "default-branch": true, + "bin": [ + "builds/pint" + ], + "type": "project", + "autoload": { + "psr-4": { + "App\\": "app/", + "Database\\Seeders\\": "database/seeders/", + "Database\\Factories\\": "database/factories/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "An opinionated code formatter for PHP.", + "homepage": "https://laravel.com", + "keywords": [ + "format", + "formatter", + "lint", + "linter", + "php" + ], + "support": { + "issues": "https://github.com/laravel/pint/issues", + "source": "https://github.com/laravel/pint" + }, + "time": "2024-08-06T15:12:57+00:00" + }, + { + "name": "mockery/mockery", + "version": "1.7.x-dev", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "3f8d3ff1ffe4c552d45c5690c6d825e9310769bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/3f8d3ff1ffe4c552d45c5690c6d825e9310769bf", + "reference": "3f8d3ff1ffe4c552d45c5690c6d825e9310769bf", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "^2.0.1", + "lib-pcre": ">=7.0", + "php": ">=7.3" + }, + "conflict": { + "phpunit/phpunit": "<8.0" + }, + "require-dev": { + "phpunit/phpunit": ">=9.6.11 <10.4" + }, + "type": "library", + "autoload": { + "files": [ + "library/helpers.php", + "library/Mockery.php" + ], + "psr-4": { + "Mockery\\": "library/Mockery" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "https://github.com/padraic", + "role": "Author" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "docs": "https://docs.mockery.io/", + "issues": "https://github.com/mockery/mockery/issues", + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" + }, + "time": "2023-10-01T17:31:30+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "default-branch": true, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2024-06-12T14:39:25+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.1.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" + }, + "time": "2024-07-01T20:03:41+00:00" + }, + { + "name": "nunomaduro/collision", + "version": "v8.x-dev", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/collision.git", + "reference": "e7d1aa8ed753f63fa816932bbc89678238843b4a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/e7d1aa8ed753f63fa816932bbc89678238843b4a", + "reference": "e7d1aa8ed753f63fa816932bbc89678238843b4a", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.15.4", + "nunomaduro/termwind": "^2.0.1", + "php": "^8.2.0", + "symfony/console": "^7.1.3" + }, + "conflict": { + "laravel/framework": "<11.0.0 || >=12.0.0", + "phpunit/phpunit": "<10.5.1 || >=12.0.0" + }, + "require-dev": { + "larastan/larastan": "^2.9.8", + "laravel/framework": "^11.19.0", + "laravel/pint": "^1.17.1", + "laravel/sail": "^1.31.0", + "laravel/sanctum": "^4.0.2", + "laravel/tinker": "^2.9.0", + "orchestra/testbench-core": "^9.2.3", + "pestphp/pest": "^2.35.0 || ^3.0.0", + "sebastian/environment": "^6.1.0 || ^7.0.0" + }, + "default-branch": true, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" + ] + }, + "branch-alias": { + "dev-8.x": "8.x-dev" + } + }, + "autoload": { + "files": [ + "./src/Adapters/Phpunit/Autoload.php" + ], + "psr-4": { + "NunoMaduro\\Collision\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Cli error handling for console/command-line PHP applications.", + "keywords": [ + "artisan", + "cli", + "command-line", + "console", + "error", + "handling", + "laravel", + "laravel-zero", + "php", + "symfony" + ], + "support": { + "issues": "https://github.com/nunomaduro/collision/issues", + "source": "https://github.com/nunomaduro/collision" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2024-08-03T15:32:23+00:00" + }, + { + "name": "nunomaduro/termwind", + "version": "2.x-dev", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/termwind.git", + "reference": "887ab2b4773e7df58d3ebb82e50d3f6fe51a53fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/887ab2b4773e7df58d3ebb82e50d3f6fe51a53fb", + "reference": "887ab2b4773e7df58d3ebb82e50d3f6fe51a53fb", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^8.2", + "symfony/console": "^7.0.4" + }, + "require-dev": { + "ergebnis/phpstan-rules": "^2.2.0", + "illuminate/console": "^11.1.1", + "laravel/pint": "^1.15.0", + "mockery/mockery": "^1.6.11", + "pestphp/pest": "^2.34.6", + "phpstan/phpstan": "^1.10.66", + "phpstan/phpstan-strict-rules": "^1.5.2", + "symfony/var-dumper": "^7.0.4", + "thecodingmachine/phpstan-strict-rules": "^1.0.0" + }, + "default-branch": true, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Termwind\\Laravel\\TermwindServiceProvider" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Functions.php" + ], + "psr-4": { + "Termwind\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Its like Tailwind CSS, but for the console.", + "keywords": [ + "cli", + "console", + "css", + "package", + "php", + "style" + ], + "support": { + "issues": "https://github.com/nunomaduro/termwind/issues", + "source": "https://github.com/nunomaduro/termwind/tree/2.x" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://github.com/xiCO2k", + "type": "github" + } + ], + "time": "2024-06-03T18:45:50+00:00" + }, + { + "name": "pestphp/pest", + "version": "v2.34.4", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest.git", + "reference": "6a1161ead830294ef8e21fab83c0bd118b0df7cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest/zipball/6a1161ead830294ef8e21fab83c0bd118b0df7cc", + "reference": "6a1161ead830294ef8e21fab83c0bd118b0df7cc", + "shasum": "" + }, + "require": { + "brianium/paratest": "^7.3.1", + "nunomaduro/collision": "^7.10.0|^8.1.1", + "nunomaduro/termwind": "^1.15.1|^2.0.1", + "pestphp/pest-plugin": "^2.1.1", + "pestphp/pest-plugin-arch": "^2.7.0", + "php": "^8.1.0", + "phpunit/phpunit": "^10.5.13" + }, + "conflict": { + "phpunit/phpunit": ">10.5.13", + "sebastian/exporter": "<5.1.0", + "webmozart/assert": "<1.11.0" + }, + "require-dev": { + "pestphp/pest-dev-tools": "^2.16.0", + "pestphp/pest-plugin-type-coverage": "^2.8.1", + "symfony/process": "^6.4.0|^7.0.4" + }, + "bin": [ + "bin/pest" + ], + "type": "library", + "extra": { + "pest": { + "plugins": [ + "Pest\\Plugins\\Bail", + "Pest\\Plugins\\Cache", + "Pest\\Plugins\\Coverage", + "Pest\\Plugins\\Init", + "Pest\\Plugins\\Environment", + "Pest\\Plugins\\Help", + "Pest\\Plugins\\Memory", + "Pest\\Plugins\\Only", + "Pest\\Plugins\\Printer", + "Pest\\Plugins\\ProcessIsolation", + "Pest\\Plugins\\Profile", + "Pest\\Plugins\\Retry", + "Pest\\Plugins\\Snapshot", + "Pest\\Plugins\\Verbose", + "Pest\\Plugins\\Version", + "Pest\\Plugins\\Parallel" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "files": [ + "src/Functions.php", + "src/Pest.php" + ], + "psr-4": { + "Pest\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "The elegant PHP Testing Framework.", + "keywords": [ + "framework", + "pest", + "php", + "test", + "testing", + "unit" + ], + "support": { + "issues": "https://github.com/pestphp/pest/issues", + "source": "https://github.com/pestphp/pest/tree/v2.34.4" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2024-03-14T19:44:18+00:00" + }, + { + "name": "pestphp/pest-plugin", + "version": "2.x-dev", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin.git", + "reference": "52cfb1311d462ceeb58fc72b24c8b8314afdf7f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin/zipball/52cfb1311d462ceeb58fc72b24c8b8314afdf7f0", + "reference": "52cfb1311d462ceeb58fc72b24c8b8314afdf7f0", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0.0", + "composer-runtime-api": "^2.2.2", + "php": "^8.1" + }, + "conflict": { + "pestphp/pest": "<2.2.3" + }, + "require-dev": { + "composer/composer": "^2.6.6", + "pestphp/pest": "^2.30.0", + "pestphp/pest-dev-tools": "^2.16.0" + }, + "default-branch": true, + "type": "composer-plugin", + "extra": { + "class": "Pest\\Plugin\\Manager" + }, + "autoload": { + "psr-4": { + "Pest\\Plugin\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The Pest plugin manager", + "keywords": [ + "framework", + "manager", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin/tree/2.x" + }, + "funding": [ + { + "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2024-01-05T11:07:45+00:00" + }, + { + "name": "pestphp/pest-plugin-arch", + "version": "2.x-dev", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin-arch.git", + "reference": "d23b2d7498475354522c3818c42ef355dca3fcda" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin-arch/zipball/d23b2d7498475354522c3818c42ef355dca3fcda", + "reference": "d23b2d7498475354522c3818c42ef355dca3fcda", + "shasum": "" + }, + "require": { + "nunomaduro/collision": "^7.10.0|^8.1.0", + "pestphp/pest-plugin": "^2.1.1", + "php": "^8.1", + "ta-tikoma/phpunit-architecture-test": "^0.8.4" + }, + "require-dev": { + "pestphp/pest": "^2.33.0", + "pestphp/pest-dev-tools": "^2.16.0" + }, + "default-branch": true, + "type": "library", + "extra": { + "pest": { + "plugins": [ + "Pest\\Arch\\Plugin" + ] + } + }, + "autoload": { + "files": [ + "src/Autoload.php" + ], + "psr-4": { + "Pest\\Arch\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The Arch plugin for Pest PHP.", + "keywords": [ + "arch", + "architecture", + "framework", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin-arch/tree/v2.7.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2024-01-26T09:46:42+00:00" + }, + { + "name": "phar-io/manifest", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "default-branch": true, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "a0eeab580cbdf4414fef6978732510a36ed0a9d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/a0eeab580cbdf4414fef6978732510a36ed0a9d6", + "reference": "a0eeab580cbdf4414fef6978732510a36ed0a9d6", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/master" + }, + "time": "2021-06-25T13:47:51+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.x-dev", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "aa53f8d4374d1f5bd3fc598548d6272cb5d9bf39" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/aa53f8d4374d1f5bd3fc598548d6272cb5d9bf39", + "reference": "aa53f8d4374d1f5bd3fc598548d6272cb5d9bf39", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.5", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^5.13" + }, + "default-branch": true, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.x" + }, + "time": "2024-05-21T06:14:15+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "eee054a3d40f09920f5b27c9b09a6483f88d9d24" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/eee054a3d40f09920f5b27c9b09a6483f88d9d24", + "reference": "eee054a3d40f09920f5b27c9b09a6483f88d9d24", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.18" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" + }, + "default-branch": true, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.x" + }, + "time": "2024-05-24T14:24:30+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.29.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fcaefacf2d5c417e928405b71b400d4ce10daaf4", + "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^4.15", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.1" + }, + "time": "2024-05-31T08:52:43+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "10.1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "09d5f55e54c20f2353a790d09772db3ad577b9ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/09d5f55e54c20f2353a790d09772db3ad577b9ac", + "reference": "09d5f55e54c20f2353a790d09772db3ad577b9ac", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1", + "phpunit/php-file-iterator": "^4.0", + "phpunit/php-text-template": "^3.0", + "sebastian/code-unit-reverse-lookup": "^3.0", + "sebastian/complexity": "^3.0", + "sebastian/environment": "^6.0", + "sebastian/lines-of-code": "^2.0", + "sebastian/version": "^4.0", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.1" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "10.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-17T05:15:22+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "4.1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "5b9a71d1d40049fe474f1cef19f170296eb4cde5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/5b9a71d1d40049fe474f1cef19f170296eb4cde5", + "reference": "5b9a71d1d40049fe474f1cef19f170296eb4cde5", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-17T06:34:53+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "4.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "af802612ce2d11906cc5b508189206480b26c027" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/af802612ce2d11906cc5b508189206480b26c027", + "reference": "af802612ce2d11906cc5b508189206480b26c027", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-17T06:35:16+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "3.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "6e773cc9df58d063ceef93234c633e8dc50d21b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/6e773cc9df58d063ceef93234c633e8dc50d21b1", + "reference": "6e773cc9df58d063ceef93234c633e8dc50d21b1", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-17T06:35:41+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "6.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "404370c0c9d97979a7a36348ee4451d3c3504314" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/404370c0c9d97979a7a36348ee4451d3c3504314", + "reference": "404370c0c9d97979a7a36348ee4451d3c3504314", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-17T06:36:03+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "10.5.13", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "20a63fc1c6db29b15da3bd02d4b6cf59900088a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/20a63fc1c6db29b15da3bd02d4b6cf59900088a7", + "reference": "20a63fc1c6db29b15da3bd02d4b6cf59900088a7", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=8.1", + "phpunit/php-code-coverage": "^10.1.5", + "phpunit/php-file-iterator": "^4.0", + "phpunit/php-invoker": "^4.0", + "phpunit/php-text-template": "^3.0", + "phpunit/php-timer": "^6.0", + "sebastian/cli-parser": "^2.0", + "sebastian/code-unit": "^2.0", + "sebastian/comparator": "^5.0", + "sebastian/diff": "^5.0", + "sebastian/environment": "^6.0", + "sebastian/exporter": "^5.1", + "sebastian/global-state": "^6.0.1", + "sebastian/object-enumerator": "^5.0", + "sebastian/recursion-context": "^5.0", + "sebastian/type": "^4.0", + "sebastian/version": "^4.0" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "10.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.13" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2024-03-12T15:37:41+00:00" + }, + { + "name": "psr/log", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "default-branch": true, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "2.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "2694214f88b081aefd254d6199600e3fbf3e7f5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2694214f88b081aefd254d6199600e3fbf3e7f5e", + "reference": "2694214f88b081aefd254d6199600e3fbf3e7f5e", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-17T06:10:12+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "2.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "53937a3f64b64cc2a324dcf25889166a0dfdc236" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/53937a3f64b64cc2a324dcf25889166a0dfdc236", + "reference": "53937a3f64b64cc2a324dcf25889166a0dfdc236", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-17T06:25:47+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "3.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "56e67fc2d11653dd91c9d3a51bc16b678b110061" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/56e67fc2d11653dd91c9d3a51bc16b678b110061", + "reference": "56e67fc2d11653dd91c9d3a51bc16b678b110061", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-17T06:28:02+00:00" + }, + { + "name": "sebastian/comparator", + "version": "5.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53", + "reference": "2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/diff": "^5.0", + "sebastian/exporter": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-12T06:03:08+00:00" + }, + { + "name": "sebastian/complexity", + "version": "3.2.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "32ea51fa0f24ec72c98c53e2ce78ff06e5bfbafe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/32ea51fa0f24ec72c98c53e2ce78ff06e5bfbafe", + "reference": "32ea51fa0f24ec72c98c53e2ce78ff06e5bfbafe", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/3.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-17T06:34:07+00:00" + }, + { + "name": "sebastian/diff", + "version": "5.1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "ee6a6fefbf48cac376d8eec272585024f42ff7a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ee6a6fefbf48cac376d8eec272585024f42ff7a3", + "reference": "ee6a6fefbf48cac376d8eec272585024f42ff7a3", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0", + "symfony/process": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/5.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-17T06:30:13+00:00" + }, + { + "name": "sebastian/environment", + "version": "6.1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "bae6ff4114353f650293dd3bf4d4ce2c7b321cf1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/bae6ff4114353f650293dd3bf4d4ce2c7b321cf1", + "reference": "bae6ff4114353f650293dd3bf4d4ce2c7b321cf1", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/6.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-17T06:30:33+00:00" + }, + { + "name": "sebastian/exporter", + "version": "5.1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "c328b4260bfe7b25187d5cab3859fdc9079218f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c328b4260bfe7b25187d5cab3859fdc9079218f2", + "reference": "c328b4260bfe7b25187d5cab3859fdc9079218f2", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/5.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-17T06:30:53+00:00" + }, + { + "name": "sebastian/global-state", + "version": "6.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "8889ce5745aed1ca531515069f1eb01b071297b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/8889ce5745aed1ca531515069f1eb01b071297b8", + "reference": "8889ce5745aed1ca531515069f1eb01b071297b8", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/6.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-17T06:31:10+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "2.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "da76c0ecddbe676bffc36578e6ce80f7826066b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/da76c0ecddbe676bffc36578e6ce80f7826066b6", + "reference": "da76c0ecddbe676bffc36578e6ce80f7826066b6", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-17T06:31:30+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "5.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "bc0b43b8f1eeb5eb690c59dc0b7aa640220fa130" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/bc0b43b8f1eeb5eb690c59dc0b7aa640220fa130", + "reference": "bc0b43b8f1eeb5eb690c59dc0b7aa640220fa130", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-17T06:32:06+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "3.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "6e68c7fa8a96a2c4610d59128e2f70151e77725a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e68c7fa8a96a2c4610d59128e2f70151e77725a", + "reference": "6e68c7fa8a96a2c4610d59128e2f70151e77725a", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-17T06:32:36+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "5.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "000f42a8f52451ec8e73f38c14781a14ea399cf2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/000f42a8f52451ec8e73f38c14781a14ea399cf2", + "reference": "000f42a8f52451ec8e73f38c14781a14ea399cf2", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-17T06:36:38+00:00" + }, + { + "name": "sebastian/type", + "version": "4.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "01f0cde154e477e65c143ed354687b7a800712ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/01f0cde154e477e65c143ed354687b7a800712ff", + "reference": "01f0cde154e477e65c143ed354687b7a800712ff", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/4.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-17T06:36:57+00:00" + }, + { + "name": "sebastian/version", + "version": "4.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "cc835c21ef77e6200d6486e0ba2b794e95a27524" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/cc835c21ef77e6200d6486e0ba2b794e95a27524", + "reference": "cc835c21ef77e6200d6486e0ba2b794e95a27524", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/4.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-22T13:54:54+00:00" + }, + { + "name": "symfony/console", + "version": "7.2.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "2204ca36f39ffe9bc0856123d45fd6cea52a2681" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/2204ca36f39ffe9bc0856123d45fd6cea52a2681", + "reference": "2204ca36f39ffe9bc0856123d45fd6cea52a2681", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^6.4|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/7.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-08-12T10:26:02+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "dev-main", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "default-branch": true, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "0424dff1c58f028c451efff2045f5d92410bd540" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", + "reference": "0424dff1c58f028c451efff2045f5d92410bd540", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "default-branch": true, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T15:07:36+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "default-branch": true, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T15:07:36+00:00" + }, + { + "name": "symfony/process", + "version": "7.2.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "bb0a8b7772610211c2cd7d6e4e36acfcbadcb613" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/bb0a8b7772610211c2cd7d6e4e36acfcbadcb613", + "reference": "bb0a8b7772610211c2cd7d6e4e36acfcbadcb613", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/7.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-07-29T06:33:22+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "dev-main", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "default-branch": true, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/string", + "version": "7.2.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "c38cbd6dcf2a45bcfbd79fb89d6bd08f7afdc7dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/c38cbd6dcf2a45bcfbd79fb89d6bd08f7afdc7dc", + "reference": "c38cbd6dcf2a45bcfbd79fb89d6bd08f7afdc7dc", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1", + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/7.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-08-12T10:26:02+00:00" + }, + { + "name": "ta-tikoma/phpunit-architecture-test", + "version": "0.8.4", + "source": { + "type": "git", + "url": "https://github.com/ta-tikoma/phpunit-architecture-test.git", + "reference": "89f0dea1cb0f0d5744d3ec1764a286af5e006636" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ta-tikoma/phpunit-architecture-test/zipball/89f0dea1cb0f0d5744d3ec1764a286af5e006636", + "reference": "89f0dea1cb0f0d5744d3ec1764a286af5e006636", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18.0 || ^5.0.0", + "php": "^8.1.0", + "phpdocumentor/reflection-docblock": "^5.3.0", + "phpunit/phpunit": "^10.5.5 || ^11.0.0", + "symfony/finder": "^6.4.0 || ^7.0.0" + }, + "require-dev": { + "laravel/pint": "^1.13.7", + "phpstan/phpstan": "^1.10.52" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPUnit\\Architecture\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ni Shi", + "email": "futik0ma011@gmail.com" + }, + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Methods for testing application architecture", + "keywords": [ + "architecture", + "phpunit", + "stucture", + "test", + "testing" + ], + "support": { + "issues": "https://github.com/ta-tikoma/phpunit-architecture-test/issues", + "source": "https://github.com/ta-tikoma/phpunit-architecture-test/tree/0.8.4" + }, + "time": "2024-01-05T14:10:56+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.11.0" + }, + "time": "2022-06-03T18:03:27+00:00" + } + ], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": { + "laravel/pint": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.0" + }, + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/config/config.php b/config/config.php new file mode 100644 index 0000000..985b250 --- /dev/null +++ b/config/config.php @@ -0,0 +1,36 @@ + env('SHOPPING_FORMAT_VALUES', false), + + 'decimals' => env('SHOPPING_DECIMALS', 0), + + 'dec_point' => env('SHOPPING_DEC_POINT', '.'), + + 'thousands_sep' => env('SHOPPING_THOUSANDS_SEP', ','), + + /* + * --------------------------------------------------------------- + * persistence + * --------------------------------------------------------------- + * + * the configuration for persisting cart + */ + 'storage' => null, + + /* + * --------------------------------------------------------------- + * events + * --------------------------------------------------------------- + * + * the configuration for cart events + */ + 'events' => null, +]; \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..d025725 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,17 @@ + + + + + ./tests/Unit + + + + + ./src + + + diff --git a/pint.json b/pint.json new file mode 100644 index 0000000..44569af --- /dev/null +++ b/pint.json @@ -0,0 +1,12 @@ +{ + "preset": "laravel", + "rules": { + "concat_space": { + "spacing": "one" + }, + "class_attributes_separation": false, + "ordered_imports": { + "sort_algorithm": "length" + } + } +} diff --git a/src/Cart.php b/src/Cart.php new file mode 100644 index 0000000..f192a6a --- /dev/null +++ b/src/Cart.php @@ -0,0 +1,856 @@ +events = $events; + $this->session = $session; + $this->instanceName = $instanceName; + $this->sessionKey = $session_key; + $this->sessionKeyCartItems = $this->sessionKey . '_cart_items'; + $this->sessionKeyCartConditions = $this->sessionKey . '_cart_conditions'; + $this->config = $config; + $this->currentItemId = null; + $this->fireEvent('created'); + } + + /** + * sets the session key + * + * @param string $sessionKey the session key or identifier + * @return $this|bool + * + * @throws \Exception + */ + public function session($sessionKey) + { + if (! $sessionKey) { + throw new \Exception('Session key is required.'); + } + + $this->sessionKey = $sessionKey; + $this->sessionKeyCartItems = $this->sessionKey . '_cart_items'; + $this->sessionKeyCartConditions = $this->sessionKey . '_cart_conditions'; + + return $this; + } + + /** + * get instance name of the cart + * + * @return string + */ + public function getInstanceName() + { + return $this->instanceName; + } + + /** + * get an item on a cart by item ID + * + * @return mixed + */ + public function get($itemId) + { + return $this->getContent()->get($itemId); + } + + /** + * check if an item exists by item ID + * + * @return bool + */ + public function has($itemId) + { + return $this->getContent()->has($itemId); + } + + /** + * add item to the cart, it can be an array or multi dimensional array + * + * @param string|array $id + * @param string $name + * @param float $price + * @param int $quantity + * @param array $attributes + * @param CartCondition|array $conditions + * @param string $associatedModel + * @return $this + * + * @throws InvalidItemException + */ + public function add($id, $name = null, $price = null, $quantity = null, $attributes = [], $conditions = [], $associatedModel = null) + { + // if the first argument is an array, + // we will need to call add again + if (is_array($id)) { + // the first argument is an array, now we will need to check if it is a multi dimensional + // array, if so, we will iterate through each item and call add again + if (Helpers::isMultiArray($id)) { + foreach ($id as $item) { + $this->add( + $item['id'], + $item['name'], + $item['price'], + $item['quantity'], + Helpers::issetAndHasValueOrAssignDefault($item['attributes'], []), + Helpers::issetAndHasValueOrAssignDefault($item['conditions'], []), + Helpers::issetAndHasValueOrAssignDefault($item['associatedModel'], null) + ); + } + } else { + $this->add( + $id['id'], + $id['name'], + $id['price'], + $id['quantity'], + Helpers::issetAndHasValueOrAssignDefault($id['attributes'], []), + Helpers::issetAndHasValueOrAssignDefault($id['conditions'], []), + Helpers::issetAndHasValueOrAssignDefault($id['associatedModel'], null) + ); + } + + return $this; + } + + $data = [ + 'id' => $id, + 'name' => $name, + 'price' => Helpers::normalizePrice($price), + 'quantity' => $quantity, + 'attributes' => new ItemAttributeCollection($attributes), + 'conditions' => $conditions, + ]; + + if (isset($associatedModel) && $associatedModel != '') { + $data['associatedModel'] = $associatedModel; + } + + // validate data + $item = $this->validate($data); + + // get the cart + $cart = $this->getContent(); + + // if the item is already in the cart we will just update it + if ($cart->has($id)) { + + $this->update($id, $item); + } else { + + $this->addRow($id, $item); + } + + $this->currentItemId = $id; + + return $this; + } + + /** + * update a cart + * + * @param array $data + * + * the $data will be an associative array, you don't need to pass all the data, only the key value + * of the item you want to update on it + * @return bool + */ + public function update($id, $data) + { + if ($this->fireEvent('updating', $data) === false) { + return false; + } + + $cart = $this->getContent(); + + $item = $cart->pull($id); + + foreach ($data as $key => $value) { + // if the key is currently "quantity" we will need to check if an arithmetic + // symbol is present so we can decide if the update of quantity is being added + // or being reduced. + if ($key == 'quantity') { + // we will check if quantity value provided is array, + // if it is, we will need to check if a key "relative" is set + // and we will evaluate its value if true or false, + // this tells us how to treat the quantity value if it should be updated + // relatively to its current quantity value or just totally replace the value + if (is_array($value)) { + if (isset($value['relative'])) { + if ((bool) $value['relative']) { + $item = $this->updateQuantityRelative($item, $key, $value['value']); + } else { + $item = $this->updateQuantityNotRelative($item, $key, $value['value']); + } + } + } else { + $item = $this->updateQuantityRelative($item, $key, $value); + } + } elseif ($key == 'attributes') { + $item[$key] = new ItemAttributeCollection($value); + } else { + $item[$key] = $value; + } + } + + $cart->put($id, $item); + + $this->save($cart); + + $this->fireEvent('updated', $item); + + return true; + } + + /** + * Set the config + * + * @param array $config + * @return void + */ + public function setConfig($config) + { + $this->config = $config; + } + + /** + * add condition on an existing item on the cart + * + * @param int|string $productId + * @param CartCondition $itemCondition + * @return $this + */ + public function addItemCondition($productId, $itemCondition) + { + if ($product = $this->get($productId)) { + $conditionInstance = '\\Wearepixel\\Cart\\CartCondition'; + + if ($itemCondition instanceof $conditionInstance) { + // we need to copy first to a temporary variable to hold the conditions + // to avoid hitting this error "Indirect modification of overloaded element of Wearepixel\Cart\ItemCollection has no effect" + // this is due to laravel Collection instance that implements Array Access + // // see link for more info: http://stackoverflow.com/questions/20053269/indirect-modification-of-overloaded-element-of-splfixedarray-has-no-effect + $itemConditionTempHolder = $product['conditions']; + + if (is_array($itemConditionTempHolder)) { + array_push($itemConditionTempHolder, $itemCondition); + } else { + $itemConditionTempHolder = $itemCondition; + } + + $this->update($productId, [ + 'conditions' => $itemConditionTempHolder, // the newly updated conditions + ]); + } + } + + return $this; + } + + /** + * removes an item on cart by item ID + * + * @return bool + */ + public function remove($id) + { + $cart = $this->getContent(); + + if ($this->fireEvent('removing', $id) === false) { + return false; + } + + $cart->forget($id); + + $this->save($cart); + + $this->fireEvent('removed', $id); + + return true; + } + + /** + * clear cart + * + * @return bool + */ + public function clear() + { + if ($this->fireEvent('clearing') === false) { + return false; + } + + $this->session->put( + $this->sessionKeyCartItems, + [] + ); + + $this->fireEvent('cleared'); + + return true; + } + + /** + * add a condition on the cart + * + * @param CartCondition|array $condition + * @return $this + * + * @throws InvalidConditionException + */ + public function condition($condition) + { + if (is_array($condition)) { + foreach ($condition as $c) { + $this->condition($c); + } + + return $this; + } + + if (! $condition instanceof CartCondition) { + throw new InvalidConditionException('Argument 1 must be an instance of \'Wearepixel\Cart\CartCondition\''); + } + + $conditions = $this->getConditions(); + + // Check if order has been applied + if ($condition->getOrder() == 0) { + $last = $conditions->last(); + $condition->setOrder(! is_null($last) ? $last->getOrder() + 1 : 1); + } + + $conditions->put($condition->getName(), $condition); + + $conditions = $conditions->sortBy(function ($condition, $key) { + return $condition->getOrder(); + }); + + $this->saveConditions($conditions); + + return $this; + } + + /** + * get conditions applied on the cart + * + * @return CartConditionCollection + */ + public function getConditions() + { + return new CartConditionCollection($this->session->get($this->sessionKeyCartConditions)); + } + + /** + * get condition applied on the cart by its name + * + * @return CartCondition + */ + public function getCondition($conditionName) + { + return $this->getConditions()->get($conditionName); + } + + /** + * Get all the condition filtered by Type + * Please Note that this will only return condition added on cart bases, not those conditions added + * specifically on an per item bases + * + * @return CartConditionCollection + */ + public function getConditionsByType($type) + { + return $this->getConditions()->filter(function (CartCondition $condition) use ($type) { + return $condition->getType() == $type; + }); + } + + /** + * Remove all the condition with the $type specified + * Please Note that this will only remove condition added on cart bases, not those conditions added + * specifically on an per item bases + * + * @return $this + */ + public function removeConditionsByType($type) + { + $this->getConditionsByType($type)->each(function ($condition) { + $this->removeCartCondition($condition->getName()); + }); + } + + /** + * removes a condition on a cart by condition name, + * this can only remove conditions that are added on cart bases not conditions that are added on an item/product. + * If you wish to remove a condition that has been added for a specific item/product, you may + * use the removeItemCondition(itemId, conditionName) method instead. + * + * @return void + */ + public function removeCartCondition($conditionName) + { + $conditions = $this->getConditions(); + + $conditions->pull($conditionName); + + $this->saveConditions($conditions); + } + + /** + * remove a condition that has been applied on an item that is already on the cart + * + * @return bool + */ + public function removeItemCondition($itemId, $conditionName) + { + if (! $item = $this->getContent()->get($itemId)) { + return false; + } + + if ($this->itemHasConditions($item)) { + // NOTE: + // we do it this way, we get first conditions and store + // it in a temp variable $originalConditions, then we will modify the array there + // and after modification we will store it again on $item['conditions'] + // This is because of ArrayAccess implementation + // see link for more info: http://stackoverflow.com/questions/20053269/indirect-modification-of-overloaded-element-of-splfixedarray-has-no-effect + + $tempConditionsHolder = $item['conditions']; + + // if the item's conditions is in array format + // we will iterate through all of it and check if the name matches + // to the given name the user wants to remove, if so, remove it + if (is_array($tempConditionsHolder)) { + foreach ($tempConditionsHolder as $k => $condition) { + if ($condition->getName() == $conditionName) { + unset($tempConditionsHolder[$k]); + } + } + + $item['conditions'] = $tempConditionsHolder; + } + + // if the item condition is not an array, we will check if it is + // an instance of a Condition, if so, we will check if the name matches + // on the given condition name the user wants to remove, if so, + // lets just make $item['conditions'] an empty array as there's just 1 condition on it anyway + else { + $conditionInstance = 'Wearepixel\\Cart\\CartCondition'; + + if ($item['conditions'] instanceof $conditionInstance) { + if ($tempConditionsHolder->getName() == $conditionName) { + $item['conditions'] = []; + } + } + } + } + + $this->update($itemId, [ + 'conditions' => $item['conditions'], + ]); + + return true; + } + + /** + * remove all conditions that has been applied on an item that is already on the cart + * + * @return bool + */ + public function clearItemConditions($itemId) + { + if (! $item = $this->getContent()->get($itemId)) { + return false; + } + + $this->update($itemId, [ + 'conditions' => [], + ]); + + return true; + } + + /** + * clears all conditions on a cart, + * this does not remove conditions that has been added specifically to an item/product. + * If you wish to remove a specific condition to a product, you may use the method: removeItemCondition($itemId, $conditionName) + * + * @return void + */ + public function clearCartConditions() + { + $this->session->put( + $this->sessionKeyCartConditions, + [] + ); + } + + /** + * get cart sub total without conditions + * + * @param bool $formatted + * @return float + */ + public function getSubTotalWithoutConditions($formatted = true) + { + $cart = $this->getContent(); + + $sum = $cart->sum(function ($item) { + return $item->getPriceSum(); + }); + + return Helpers::formatValue(floatval($sum), $formatted, $this->config); + } + + /** + * get cart sub total + * + * @param bool $formatted + * @return float + */ + public function getSubTotal($formatted = true) + { + $cart = $this->getContent(); + + $sum = $cart->sum(function (ItemCollection $item) { + return $item->getPriceSumWithConditions(false); + }); + + // get the conditions that are meant to be applied + // on the subtotal and apply it here before returning the subtotal + $conditions = $this + ->getConditions() + ->filter(function (CartCondition $cond) { + return $cond->getTarget() === 'subtotal'; + }); + + // if there is no conditions, lets just return the sum + if (! $conditions->count()) { + return Helpers::formatValue($sum, $formatted, $this->config); + } + + // there are conditions, lets apply it + $newTotal = 0.00; + $process = 0; + + $conditions->each(function (CartCondition $cond) use ($sum, &$newTotal, &$process) { + + // if this is the first iteration, the toBeCalculated + // should be the sum as initial point of value. + $toBeCalculated = ($process > 0) ? $newTotal : $sum; + + $newTotal = $cond->applyCondition($toBeCalculated); + + $process++; + }); + + return Helpers::formatValue((float) $newTotal, $formatted, $this->config); + } + + /** + * the new total in which conditions are already applied + * + * @return float + */ + public function getTotal() + { + $subTotal = $this->getSubTotal(false); + + $newTotal = 0.00; + + $process = 0; + + $conditions = $this + ->getConditions() + ->filter(function (CartCondition $cond) { + return $cond->getTarget() === 'total'; + }); + + // if no conditions were added, just return the sub total + if (! $conditions->count()) { + return Helpers::formatValue($subTotal, $this->config['format_numbers'], $this->config); + } + + $conditions + ->each(function (CartCondition $cond) use ($subTotal, &$newTotal, &$process) { + $toBeCalculated = ($process > 0) ? $newTotal : $subTotal; + + $newTotal = $cond->applyCondition($toBeCalculated); + + $process++; + }); + + return Helpers::formatValue($newTotal, $this->config['format_numbers'], $this->config); + } + + /** + * get total quantity of items in the cart + * + * @return int + */ + public function getTotalQuantity() + { + $items = $this->getContent(); + + if ($items->isEmpty()) { + return 0; + } + + $count = $items->sum(function ($item) { + return $item['quantity']; + }); + + return $count; + } + + /** + * get the cart + * + * @return CartCollection + */ + public function getContent() + { + return (new CartCollection($this->session->get($this->sessionKeyCartItems)))->reject(function ($item) { + return ! ($item instanceof ItemCollection); + }); + } + + /** + * check if cart is empty + * + * @return bool + */ + public function isEmpty() + { + return $this->getContent()->isEmpty(); + } + + /** + * validate Item data + * + * @return array $item; + * + * @throws InvalidItemException + */ + protected function validate($item) + { + $rules = [ + 'id' => 'required', + 'price' => 'required|numeric', + 'quantity' => 'required|numeric|min:0.1', + 'name' => 'required', + ]; + + $validator = CartItemValidator::make($item, $rules); + + if ($validator->fails()) { + throw new InvalidItemException($validator->messages()->first()); + } + + return $item; + } + + /** + * add row to cart collection + * + * @return bool + */ + protected function addRow($id, $item) + { + if ($this->fireEvent('adding', $item) === false) { + return false; + } + + $cart = $this->getContent(); + + $cart->put($id, new ItemCollection($item, $this->config)); + + $this->save($cart); + + $this->fireEvent('added', $item); + + return true; + } + + /** + * save the cart + * + * @param $cart CartCollection + */ + protected function save($cart) + { + $this->session->put($this->sessionKeyCartItems, $cart); + } + + /** + * save the cart conditions + */ + protected function saveConditions($conditions) + { + $this->session->put($this->sessionKeyCartConditions, $conditions); + } + + /** + * check if an item has condition + * + * @return bool + */ + protected function itemHasConditions($item) + { + if (! isset($item['conditions'])) { + return false; + } + + if (is_array($item['conditions'])) { + return count($item['conditions']) > 0; + } + + $conditionInstance = 'Wearepixel\\Cart\\CartCondition'; + + if ($item['conditions'] instanceof $conditionInstance) { + return true; + } + + return false; + } + + /** + * update a cart item quantity relative to its current quantity + * + * @return mixed + */ + protected function updateQuantityRelative($item, $key, $value) + { + if (preg_match('/\-/', $value) == 1) { + $value = (int) str_replace('-', '', $value); + + // we will not allowed to reduced quantity to 0, so if the given value + // would result to item quantity of 0, we will not do it. + if (($item[$key] - $value) > 0) { + $item[$key] -= $value; + } + } elseif (preg_match('/\+/', $value) == 1) { + $item[$key] += (int) str_replace('+', '', $value); + } else { + $item[$key] += (int) $value; + } + + return $item; + } + + /** + * update cart item quantity not relative to its current quantity value + * + * @return mixed + */ + protected function updateQuantityNotRelative($item, $key, $value) + { + $item[$key] = (int) $value; + + return $item; + } + + /** + * Setter for decimals. Change value on demand. + */ + public function setDecimals($decimals) + { + $this->decimals = $decimals; + } + + /** + * @return mixed + */ + protected function fireEvent($name, $value = []) + { + return $this->events->dispatch($this->getInstanceName() . '.' . $name, array_values([$value, $this]), true); + } + + /** + * Associate the cart item with the given id with the given model. + * + * @param string $id + * @param mixed $model + * @return void + */ + public function associate($model) + { + if (is_string($model) && ! class_exists($model)) { + throw new UnknownModelException("The supplied model {$model} does not exist."); + } + + $cart = $this->getContent(); + + $item = $cart->pull($this->currentItemId); + + $item['associatedModel'] = $model; + + $cart->put($this->currentItemId, new ItemCollection($item, $this->config)); + + $this->save($cart); + + return $this; + } +} diff --git a/src/CartCollection.php b/src/CartCollection.php new file mode 100644 index 0000000..61b4a2d --- /dev/null +++ b/src/CartCollection.php @@ -0,0 +1,9 @@ +args = $args; + + if (Helpers::isMultiArray($args)) { + throw new InvalidConditionException('Multi dimensional array is not supported.'); + } else { + $this->validate($this->args); + } + } + + /** + * the target of where the condition is applied. + * NOTE: On conditions added to per item bases, target is not needed. + * + * @return mixed + */ + public function getTarget() + { + return (isset($this->args['target'])) ? $this->args['target'] : ''; + } + + /** + * the name of the condition + * + * @return mixed + */ + public function getName() + { + return $this->args['name']; + } + + /** + * the type of the condition + * + * @return mixed + */ + public function getType() + { + return $this->args['type']; + } + + /** + * get the additional attributes of a condition + * + * @return array + */ + public function getAttributes() + { + return (isset($this->args['attributes'])) ? $this->args['attributes'] : []; + } + + /** + * the value of this the condition + * + * @return mixed + */ + public function getValue() + { + return $this->args['value']; + } + + /** + * Set the order to apply this condition. If no argument order is applied we return 0 as + * indicator that no assignment has been made + * + * @param int $order + * @return int + */ + public function setOrder($order = 1) + { + $this->args['order'] = $order; + } + + /** + * the order to apply this condition. If no argument order is applied we return 0 as + * indicator that no assignment has been made + * + * @return int + */ + public function getOrder() + { + return isset($this->args['order']) && is_numeric($this->args['order']) ? (int) $this->args['order'] : 0; + } + + /** + * apply condition to total or subtotal + * + * @return float + */ + public function applyCondition($totalOrSubTotalOrPrice) + { + return $this->apply($totalOrSubTotalOrPrice, $this->getValue()); + } + + /** + * get the calculated value of this condition supplied by the subtotal|price + * + * @return mixed + */ + public function getCalculatedValue($totalOrSubTotalOrPrice) + { + $this->apply($totalOrSubTotalOrPrice, $this->getValue()); + + return $this->parsedRawValue; + } + + /** + * apply condition + * + * @return float + */ + protected function apply($totalOrSubTotalOrPrice, $conditionValue) + { + // if value has a percentage sign on it, we will get first + // its percentage then we will evaluate again if the value + // has a minus or plus sign so we can decide what to do with the + // percentage, whether to add or subtract it to the total/subtotal/price + // if we can't find any plus/minus sign, we will assume it as plus sign + if ($this->valueIsPercentage($conditionValue)) { + if ($this->valueIsToBeSubtracted($conditionValue)) { + $value = Helpers::normalizePrice($this->cleanValue($conditionValue)); + + $this->parsedRawValue = $totalOrSubTotalOrPrice * ($value / 100); + + $result = floatval($totalOrSubTotalOrPrice - $this->parsedRawValue); + } elseif ($this->valueIsToBeAdded($conditionValue)) { + $value = Helpers::normalizePrice($this->cleanValue($conditionValue)); + + $this->parsedRawValue = $totalOrSubTotalOrPrice * ($value / 100); + + $result = floatval($totalOrSubTotalOrPrice + $this->parsedRawValue); + } else { + $value = Helpers::normalizePrice($conditionValue); + + $this->parsedRawValue = $totalOrSubTotalOrPrice * ($value / 100); + + $result = floatval($totalOrSubTotalOrPrice + $this->parsedRawValue); + } + } + + // if the value has no percent sign on it, the operation will not be a percentage + // next is we will check if it has a minus/plus sign so then we can just deduct it to total/subtotal/price + else { + if ($this->valueIsToBeSubtracted($conditionValue)) { + $this->parsedRawValue = Helpers::normalizePrice($this->cleanValue($conditionValue)); + + $result = floatval($totalOrSubTotalOrPrice - $this->parsedRawValue); + } elseif ($this->valueIsToBeAdded($conditionValue)) { + $this->parsedRawValue = Helpers::normalizePrice($this->cleanValue($conditionValue)); + + $result = floatval($totalOrSubTotalOrPrice + $this->parsedRawValue); + } else { + $this->parsedRawValue = Helpers::normalizePrice($conditionValue); + + $result = floatval($totalOrSubTotalOrPrice + $this->parsedRawValue); + } + } + + // Do not allow items with negative prices. + return $result < 0 ? 0.00 : $result; + } + + /** + * check if value is a percentage + * + * @return bool + */ + protected function valueIsPercentage($value) + { + return preg_match('/%/', $value) == 1; + } + + /** + * check if value is a subtract + * + * @return bool + */ + protected function valueIsToBeSubtracted($value) + { + return preg_match('/\-/', $value) == 1; + } + + /** + * check if value is to be added + * + * @return bool + */ + protected function valueIsToBeAdded($value) + { + return preg_match('/\+/', $value) == 1; + } + + /** + * removes some arithmetic signs (%,+,-) only + * + * @return mixed + */ + protected function cleanValue($value) + { + return str_replace(['%', '-', '+'], '', $value); + } + + /** + * validates condition arguments + * + * @throws InvalidConditionException + */ + protected function validate($args) + { + $rules = [ + 'name' => 'required', + 'type' => 'required', + 'value' => 'required', + ]; + + $validator = CartConditionValidator::make($args, $rules); + + if ($validator->fails()) { + throw new InvalidConditionException($validator->messages()->first()); + } + } +} diff --git a/src/CartConditionCollection.php b/src/CartConditionCollection.php new file mode 100644 index 0000000..23c24c0 --- /dev/null +++ b/src/CartConditionCollection.php @@ -0,0 +1,7 @@ +app->runningInConsole()) { + $this->publishes([ + __DIR__ . '/../config/config.php' => config_path('cart.php'), + ], 'config'); + } + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + $this->mergeConfigFrom(__DIR__ . '/../config/config.php', 'cart'); + + $this->app->singleton('cart', function ($app) { + $storageClass = config('cart.storage'); + $eventsClass = config('cart.events'); + + $storage = $storageClass ? new $storageClass : $app['session']; + $events = $eventsClass ? new $eventsClass : $app['events']; + $instanceName = 'cart'; + + // default session or cart identifier. This will be overridden when calling Cart::session($sessionKey)->add() etc.. + // like when adding a cart for a specific user name. Session Key can be string or maybe a unique identifier to bind a cart + // to a specific user, this can also be a user ID + $session_key = '4yTlTDKu3oJOfzD'; + + return new Cart( + $storage, + $events, + $instanceName, + $session_key, + config('cart') + ); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return []; + } +} diff --git a/src/Exceptions/InvalidConditionException.php b/src/Exceptions/InvalidConditionException.php new file mode 100644 index 0000000..d237983 --- /dev/null +++ b/src/Exceptions/InvalidConditionException.php @@ -0,0 +1,5 @@ + $v) { + if (is_array($v)) { + return true; + } else { + return false; + } + } + + } + } + + /** + * check if variable is set and has value, return a default value + * + * @param bool|mixed $default + * @return bool|mixed + */ + public static function issetAndHasValueOrAssignDefault(&$var, $default = false) + { + if ((isset($var)) && ($var != '')) { + return $var; + } + + return $default; + } + + public static function formatValue($value, $formatNumber, $config) + { + if ($formatNumber || $config['format_numbers']) { + return floatval(number_format($value, $config['decimals'], $config['dec_point'], $config['thousands_sep'])); + } else { + return $value; + } + } +} diff --git a/src/ItemAttributeCollection.php b/src/ItemAttributeCollection.php new file mode 100644 index 0000000..da04a69 --- /dev/null +++ b/src/ItemAttributeCollection.php @@ -0,0 +1,17 @@ +has($name)) { + return $this->get($name); + } + + return null; + } +} diff --git a/src/ItemCollection.php b/src/ItemCollection.php new file mode 100644 index 0000000..f45d907 --- /dev/null +++ b/src/ItemCollection.php @@ -0,0 +1,137 @@ +config = $config; + } + + /** + * get the sum of price + * + * @return mixed|null + */ + public function getPriceSum() + { + return Helpers::formatValue($this->price * $this->quantity, $this->config['format_numbers'], $this->config); + } + + public function __get($name) + { + if ($this->has($name) || $name == 'model') { + return ! is_null($this->get($name)) ? $this->get($name) : $this->getAssociatedModel(); + } + + return null; + } + + /** + * return the associated model of an item + * + * @return bool + */ + protected function getAssociatedModel() + { + if (! $this->has('associatedModel')) { + return null; + } + + $associatedModel = $this->get('associatedModel'); + + return with(new $associatedModel)->find($this->get('id')); + } + + /** + * check if item has conditions + * + * @return bool + */ + public function hasConditions() + { + if (! isset($this['conditions'])) { + return false; + } + if (is_array($this['conditions'])) { + return count($this['conditions']) > 0; + } + $conditionInstance = 'Wearepixel\\Cart\\CartCondition'; + if ($this['conditions'] instanceof $conditionInstance) { + return true; + } + + return false; + } + + /** + * check if item has conditions + * + * @return mixed|null + */ + public function getConditions() + { + if (! $this->hasConditions()) { + return []; + } + + return $this['conditions']; + } + + /** + * get the single price in which conditions are already applied + * + * @param bool $formatted + * @return mixed|null + */ + public function getPriceWithConditions($formatted = true) + { + $originalPrice = $this->price; + + $newPrice = 0.00; + $processed = 0; + + if ($this->hasConditions()) { + if (is_array($this->conditions)) { + foreach ($this->conditions as $condition) { + ($processed > 0) ? $toBeCalculated = $newPrice : $toBeCalculated = $originalPrice; + $newPrice = $condition->applyCondition($toBeCalculated); + $processed++; + } + } else { + $newPrice = $this['conditions']->applyCondition($originalPrice); + } + + return Helpers::formatValue($newPrice, $formatted, $this->config); + } + + return Helpers::formatValue($originalPrice, $formatted, $this->config); + } + + /** + * get the sum of price in which conditions are already applied + * + * @param bool $formatted + * @return mixed|null + */ + public function getPriceSumWithConditions($formatted = true) + { + return Helpers::formatValue($this->getPriceWithConditions(false) * $this->quantity, $formatted, $this->config); + } +} diff --git a/src/Validators/CartConditionValidator.php b/src/Validators/CartConditionValidator.php new file mode 100644 index 0000000..8cb4b8d --- /dev/null +++ b/src/Validators/CartConditionValidator.php @@ -0,0 +1,5 @@ +$method(); + + case 1: + return $instance->$method($args[0]); + + case 2: + return $instance->$method($args[0], $args[1]); + + case 3: + return $instance->$method($args[0], $args[1], $args[2]); + + case 4: + return $instance->$method($args[0], $args[1], $args[2], $args[3]); + + default: + return call_user_func_array([$instance, $method], $args); + } + } +} diff --git a/tests/Helpers/ConfigMock.php b/tests/Helpers/ConfigMock.php new file mode 100644 index 0000000..a468f65 --- /dev/null +++ b/tests/Helpers/ConfigMock.php @@ -0,0 +1,8 @@ + false, + 'decimals' => 2, + 'dec_point' => '.', + 'thousands_sep' => ',', +]; diff --git a/tests/Helpers/ConfigMockOtherFormat.php b/tests/Helpers/ConfigMockOtherFormat.php new file mode 100644 index 0000000..01cfec5 --- /dev/null +++ b/tests/Helpers/ConfigMockOtherFormat.php @@ -0,0 +1,8 @@ + false, + 'decimals' => 3, + 'dec_point' => ',', + 'thousands_sep' => '.', +]; diff --git a/tests/Helpers/MockProduct.php b/tests/Helpers/MockProduct.php new file mode 100644 index 0000000..c109070 --- /dev/null +++ b/tests/Helpers/MockProduct.php @@ -0,0 +1,88 @@ +id = $id; + $this->name = $name; + $this->price = $price; + $this->weight = $weight; + } + + /** + * Get the identifier of the item. + * + * @return int|string + */ + public function getIdentifier($options = null) + { + return $this->id; + } + + /** + * Get the description or title of the item. + * + * @return string + */ + public function getDescription($options = null) + { + return $this->name; + } + + /** + * Get the price of the item. + * + * @return float + */ + public function getPrice($options = null) + { + return $this->price; + } + + /** + * Get the price of the item. + * + * @return float + */ + public function getWeight($options = null) + { + return $this->weight; + } + + public function find($id) + { + return $this; + } +} diff --git a/tests/Helpers/SessionMock.php b/tests/Helpers/SessionMock.php new file mode 100644 index 0000000..329c860 --- /dev/null +++ b/tests/Helpers/SessionMock.php @@ -0,0 +1,23 @@ +session[$key]); + } + + public function get($key) + { + return (isset($this->session[$key])) ? $this->session[$key] : null; + } + + public function put($key, $value) + { + $this->session[$key] = $value; + } +} diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 0000000..a638bfa --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,69 @@ +extend(Tests\TestCase::class)->in('Feature'); + +/* +|-------------------------------------------------------------------------- +| Expectations +|-------------------------------------------------------------------------- +| +| When you're writing tests, you often need to check that values meet certain conditions. The +| "expect()" function gives you access to a set of "expectations" methods that you can use +| to assert different things. Of course, you may extend the Expectation API at any time. +| +*/ + +expect()->extend('toBeOne', function () { + return $this->toBe(1); +}); + +/* +|-------------------------------------------------------------------------- +| Functions +|-------------------------------------------------------------------------- +| +| While Pest is very powerful out-of-the-box, you may have some testing code specific to your +| project that you don't want to repeat in every file. Here you can also expose helpers as +| global functions to help you to reduce the number of lines of code in your test files. +| +*/ + +function fillCart() +{ + $items = [ + [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 67.99, + 'quantity' => 1, + 'attributes' => [], + ], + [ + 'id' => 568, + 'name' => 'Sample Item 2', + 'price' => 69.25, + 'quantity' => 1, + 'attributes' => [], + ], + [ + 'id' => 856, + 'name' => 'Sample Item 3', + 'price' => 50.25, + 'quantity' => 1, + 'attributes' => [], + ], + ]; + + $this->cart->add($items); +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..cfb05b6 --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,10 @@ +shouldReceive('dispatch'); + + $this->cart = new Cart( + new SessionMock, + $events, + 'cart', + 'SAMPLESESSIONKEY', + require (__DIR__ . '/../Helpers/ConfigMock.php') + ); + + $items = [ + [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 67.99, + 'quantity' => 1, + 'attributes' => [], + ], + [ + 'id' => 568, + 'name' => 'Sample Item 2', + 'price' => 69.25, + 'quantity' => 1, + 'attributes' => [], + ], + [ + 'id' => 856, + 'name' => 'Sample Item 3', + 'price' => 50.25, + 'quantity' => 1, + 'attributes' => [], + ], + ]; + + $this->cart->add($items); +}); + +afterEach(function () { + Mockery::close(); +}); + +test('subtotal', function () { + // add condition to subtotal + $condition = new CartCondition([ + 'name' => 'VAT 12.5%', + 'type' => 'tax', + 'target' => 'subtotal', + 'value' => '-5', + ]); + + $this->cart->condition($condition); + + expect($this->cart->getSubTotal())->toEqual(182.49); + + // the total is also should be the same with sub total since our getTotal + // also depends on what is the value of subtotal + expect($this->cart->getTotal())->toEqual(182.49); +}); + +test('total without condition', function () { + expect($this->cart->getSubTotal())->toEqual(187.49, 'Cart should have sub total of 187.49'); + + // no changes in subtotal as the condition's target added was for total + expect($this->cart->getSubTotal())->toEqual(187.49, 'Cart should have sub total of 187.49'); + + // total should be the same as subtotal + expect($this->cart->getTotal())->toEqual(187.49, 'Cart should have a total of 187.49'); +}); + +test('total with condition', function () { + expect($this->cart->getSubTotal())->toEqual(187.49, 'Cart should have sub total of 187.49'); + + // add condition + $condition = new CartCondition([ + 'name' => 'VAT 12.5%', + 'type' => 'tax', + 'target' => 'total', + 'value' => '12.5%', + ]); + + $this->cart->condition($condition); + + // no changes in subtotal as the condition's target added was for total + expect($this->cart->getSubTotal())->toEqual(187.49, 'Cart should have sub total of 187.49'); + + // total should be changed + $this->cart->setDecimals(5); + + expect($this->cart->getTotal())->toEqual(210.92625, 'Cart should have a total of 210.92625'); +}); + +test('total with multiple conditions added scenario one', function () { + expect($this->cart->getSubTotal())->toEqual(187.49, 'Cart should have sub total of 187.49'); + + // add condition + $condition1 = new CartCondition([ + 'name' => 'VAT 12.5%', + 'type' => 'tax', + 'target' => 'total', + 'value' => '12.5%', + ]); + + $condition2 = new CartCondition([ + 'name' => 'Express Shipping $15', + 'type' => 'shipping', + 'target' => 'total', + 'value' => '+15', + ]); + + $this->cart->condition($condition1); + $this->cart->condition($condition2); + + // no changes in subtotal as the condition's target added was for subtotal + expect($this->cart->getSubTotal())->toEqual(187.49, 'Cart should have sub total of 187.49'); + + // total should be changed + $this->cart->setDecimals(5); + expect($this->cart->getTotal())->toEqual(225.92625, 'Cart should have a total of 225.92625'); +}); + +test('total with multiple conditions added scenario two', function () { + expect($this->cart->getSubTotal())->toEqual(187.49, 'Cart should have sub total of 187.49'); + + // add condition + $condition1 = new CartCondition([ + 'name' => 'VAT 12.5%', + 'type' => 'tax', + 'target' => 'total', + 'value' => '12.5%', + ]); + $condition2 = new CartCondition([ + 'name' => 'Express Shipping $15', + 'type' => 'shipping', + 'target' => 'total', + 'value' => '-15', + ]); + + $this->cart->condition($condition1); + $this->cart->condition($condition2); + + // no changes in subtotal as the condition's target added was for subtotal + expect($this->cart->getSubTotal())->toEqual(187.49, 'Cart should have sub total of 187.49'); + + // total should be changed + $this->cart->setDecimals(5); + expect($this->cart->getTotal())->toEqual(195.92625, 'Cart should have a total of 195.92625'); +}); + +test('total with multiple conditions added scenario three', function () { + expect($this->cart->getSubTotal())->toEqual(187.49, 'Cart should have sub total of 187.49'); + + // add condition + $condition1 = new CartCondition([ + 'name' => 'VAT 12.5%', + 'type' => 'tax', + 'target' => 'total', + 'value' => '-12.5%', + ]); + $condition2 = new CartCondition([ + 'name' => 'Express Shipping $15', + 'type' => 'shipping', + 'target' => 'total', + 'value' => '-15', + ]); + + $this->cart->condition($condition1); + $this->cart->condition($condition2); + + // no changes in subtotal as the condition's target added was for total + expect($this->cart->getSubTotal())->toEqual(187.49, 'Cart should have sub total of 187.49'); + + // total should be changed + $this->cart->setDecimals(5); + expect($this->cart->getTotal())->toEqual(149.05375, 'Cart should have a total of 149.05375'); +}); + +test('cart multiple conditions can be added once by array', function () { + expect($this->cart->getSubTotal())->toEqual(187.49, 'Cart should have sub total of 187.49'); + + // add condition + $condition1 = new CartCondition([ + 'name' => 'VAT 12.5%', + 'type' => 'tax', + 'target' => 'total', + 'value' => '-12.5%', + ]); + $condition2 = new CartCondition([ + 'name' => 'Express Shipping $15', + 'type' => 'shipping', + 'target' => 'total', + 'value' => '-15', + ]); + + $this->cart->condition([$condition1, $condition2]); + + // no changes in subtotal as the condition's target added was for total + expect($this->cart->getSubTotal())->toEqual(187.49, 'Cart should have sub total of 187.49'); + + // total should be changed + $this->cart->setDecimals(5); + expect($this->cart->getTotal())->toEqual(149.05375, 'Cart should have a total of 149.05375'); +}); + +test('total with multiple conditions added scenario four', function () { + expect($this->cart->getSubTotal())->toEqual(187.49, 'Cart should have sub total of 187.49'); + + // add condition + $condition1 = new CartCondition([ + 'name' => 'COUPON LESS 12.5%', + 'type' => 'tax', + 'target' => 'total', + 'value' => '-12.5%', + ]); + $condition2 = new CartCondition([ + 'name' => 'Express Shipping $15', + 'type' => 'shipping', + 'target' => 'total', + 'value' => '+15', + ]); + + $this->cart->condition($condition1); + $this->cart->condition($condition2); + + // no changes in subtotal as the condition's target added was for total + expect($this->cart->getSubTotal())->toEqual(187.49, 'Cart should have sub total of 187.49'); + + // total should be changed + $this->cart->setDecimals(5); + expect($this->cart->getTotal())->toEqual(179.05375, 'Cart should have a total of 179.05375'); +}); + +test('add item with condition', function () { + $this->cart->clear(); + + $condition1 = new CartCondition([ + 'name' => 'SALE 5%', + 'type' => 'tax', + 'value' => '-5%', + ]); + + $item = [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 100, + 'quantity' => 1, + 'attributes' => [], + 'conditions' => $condition1, + ]; + + $this->cart->add($item); + + expect($this->cart->get(456)->getPriceSumWithConditions())->toEqual(95); + expect($this->cart->getSubTotal())->toEqual(95); +}); + +test('add item with multiple item conditions in multiple condition instance', function () { + $this->cart->clear(); + + $itemCondition1 = new CartCondition([ + 'name' => 'SALE 5%', + 'type' => 'sale', + 'value' => '-5%', + ]); + $itemCondition2 = new CartCondition([ + 'name' => 'Item Gift Pack 25.00', + 'type' => 'promo', + 'value' => '-25', + ]); + $itemCondition3 = new CartCondition([ + 'name' => 'MISC', + 'type' => 'misc', + 'value' => '+10', + ]); + + $item = [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 100, + 'quantity' => 1, + 'attributes' => [], + 'conditions' => [$itemCondition1, $itemCondition2, $itemCondition3], + ]; + + $this->cart->add($item); + + expect($this->cart->get(456)->getPriceSumWithConditions())->toEqual(80.00, 'Item subtotal with 1 item should be 80'); + expect($this->cart->getSubTotal())->toEqual(80.00, 'Cart subtotal with 1 item should be 80'); +}); + +test('add item with multiple item conditions with target omitted', function () { + $this->cart->clear(); + + // NOTE: + // $condition1 and $condition4 should not be included in calculation + // as the target is not for item, remember that when adding + // conditions in per-item bases, the condition's target should + // have a value of item + $itemCondition2 = new CartCondition([ + 'name' => 'Item Gift Pack 25.00', + 'type' => 'promo', + 'value' => '-25', + ]); + $itemCondition3 = new CartCondition([ + 'name' => 'MISC', + 'type' => 'misc', + 'value' => '+10', + ]); + + $item = [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 100, + 'quantity' => 1, + 'attributes' => [], + 'conditions' => [$itemCondition2, $itemCondition3], + ]; + + $this->cart->add($item); + + expect($this->cart->get(456)->getPriceSumWithConditions())->toEqual(85.00, 'Cart subtotal with 1 item should be 85'); + expect($this->cart->getSubTotal())->toEqual(85.00, 'Cart subtotal with 1 item should be 85'); +}); + +test('add item condition', function () { + $itemCondition2 = new CartCondition([ + 'name' => 'Item Gift Pack 25.00', + 'type' => 'promo', + 'value' => '-25', + ]); + $coupon101 = new CartCondition([ + 'name' => 'COUPON 101', + 'type' => 'coupon', + 'value' => '-5%', + ]); + + $item = [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 100, + 'quantity' => 1, + 'attributes' => [], + 'conditions' => [$itemCondition2], + ]; + + $this->cart->add($item); + + // let's prove first we have 1 condition on this item + expect($this->cart->get($item['id'])['conditions'])->toHaveCount(1, 'Item should have 1 condition'); + + // now let's insert a condition on an existing item on the cart + $this->cart->addItemCondition($item['id'], $coupon101); + + expect($this->cart->get($item['id'])['conditions'])->toHaveCount(2, 'Item should have 2 conditions'); +}); + +test('add item condition restrict negative price', function () { + $condition = new CartCondition([ + 'name' => 'Substract amount but prevent negative value', + 'type' => 'promo', + 'value' => '-25', + ]); + + $item = [ + 'id' => 789, + 'name' => 'Sample Item 1', + 'price' => 20, + 'quantity' => 1, + 'attributes' => [], + 'conditions' => [ + $condition, + ], + ]; + + $this->cart->add($item); + + // Since the product price is 20 and the condition reduces it by 25, + // check that the item's price has been prevented from dropping below zero. + expect($this->cart->get($item['id'])->getPriceSumWithConditions())->toEqual(0.00, "The item's price should be prevented from going below zero."); +}); + +test('get cart condition by condition name', function () { + $itemCondition1 = new CartCondition([ + 'name' => 'SALE 5%', + 'type' => 'sale', + 'target' => 'total', + 'value' => '-5%', + ]); + $itemCondition2 = new CartCondition([ + 'name' => 'Item Gift Pack 25.00', + 'type' => 'promo', + 'target' => 'total', + 'value' => '-25', + ]); + + $item = [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 100, + 'quantity' => 1, + 'attributes' => [], + ]; + + $this->cart->add($item); + + $this->cart->condition([$itemCondition1, $itemCondition2]); + + // get a condition applied on cart by condition name + $condition = $this->cart->getCondition($itemCondition1->getName()); + + expect('SALE 5%')->toEqual($condition->getName()); + expect('total')->toEqual($condition->getTarget()); + expect('sale')->toEqual($condition->getType()); + expect('-5%')->toEqual($condition->getValue()); +}); + +test('remove cart condition by condition name', function () { + $itemCondition1 = new CartCondition([ + 'name' => 'SALE 5%', + 'type' => 'sale', + 'target' => 'total', + 'value' => '-5%', + ]); + $itemCondition2 = new CartCondition([ + 'name' => 'Item Gift Pack 25.00', + 'type' => 'promo', + 'target' => 'total', + 'value' => '-25', + ]); + + $item = [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 100, + 'quantity' => 1, + 'attributes' => [], + ]; + + $this->cart->add($item); + + $this->cart->condition([$itemCondition1, $itemCondition2]); + + // let's prove first we have now two conditions in the cart + expect($this->cart->getConditions()->count())->toEqual(2, 'Cart should have two conditions'); + + // now let's remove a specific condition by condition name + $this->cart->removeCartCondition('SALE 5%'); + + // cart should have now only 1 condition + expect($this->cart->getConditions()->count())->toEqual(1, 'Cart should have one condition'); + expect($this->cart->getConditions()->first()->getName())->toEqual('Item Gift Pack 25.00'); +}); + +test('remove item condition by condition name', function () { + $itemCondition1 = new CartCondition([ + 'name' => 'SALE 5%', + 'type' => 'sale', + 'value' => '-5%', + ]); + $itemCondition2 = new CartCondition([ + 'name' => 'Item Gift Pack 25.00', + 'type' => 'promo', + 'value' => '-25', + ]); + + $item = [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 100, + 'quantity' => 1, + 'attributes' => [], + 'conditions' => [$itemCondition1, $itemCondition2], + ]; + + $this->cart->add($item); + + // let's very first the item has 2 conditions in it + expect($this->cart->get(456)['conditions'])->toHaveCount(2, 'Item should have two conditions'); + + // now let's remove a condition on that item using the condition name + $this->cart->removeItemCondition(456, 'SALE 5%'); + + // now we should have only 1 condition left on that item + expect($this->cart->get(456)['conditions'])->toHaveCount(1, 'Item should have one condition left'); +}); + +test('remove item condition by condition name scenario two', function () { + // NOTE: in this scenario, we will add the conditions not in array format + $itemCondition = new CartCondition([ + 'name' => 'SALE 5%', + 'type' => 'sale', + 'value' => '-5%', + ]); + + $item = [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 100, + 'quantity' => 1, + 'attributes' => [], + 'conditions' => $itemCondition, // <--not in array format + ]; + + $this->cart->add($item); + + // let's very first the item has 2 conditions in it + expect($this->cart->get(456)['conditions'])->not->toBeEmpty('Item should have one condition in it.'); + + // now let's remove a condition on that item using the condition name + $this->cart->removeItemCondition(456, 'SALE 5%'); + + // now we should have only 1 condition left on that item + expect($this->cart->get(456)['conditions'])->toBeEmpty('Item should have no condition now'); +}); + +test('clear item conditions', function () { + $itemCondition1 = new CartCondition([ + 'name' => 'SALE 5%', + 'type' => 'sale', + 'value' => '-5%', + ]); + $itemCondition2 = new CartCondition([ + 'name' => 'Item Gift Pack 25.00', + 'type' => 'promo', + 'value' => '-25', + ]); + + $item = [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 100, + 'quantity' => 1, + 'attributes' => [], + 'conditions' => [$itemCondition1, $itemCondition2], + ]; + + $this->cart->add($item); + + // let's very first the item has 2 conditions in it + expect($this->cart->get(456)['conditions'])->toHaveCount(2, 'Item should have two conditions'); + + // now let's remove all condition on that item + $this->cart->clearItemConditions(456); + + // now we should have only 0 condition left on that item + expect($this->cart->get(456)['conditions'])->toHaveCount(0, 'Item should have no conditions now'); +}); + +test('clear cart conditions', function () { + // NOTE: + // This only clears all conditions that has been added in a cart bases + // this does not remove conditions on per item bases + $itemCondition1 = new CartCondition([ + 'name' => 'SALE 5%', + 'type' => 'sale', + 'target' => 'total', + 'value' => '-5%', + ]); + $itemCondition2 = new CartCondition([ + 'name' => 'Item Gift Pack 25.00', + 'type' => 'promo', + 'target' => 'total', + 'value' => '-25', + ]); + + $item = [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 100, + 'quantity' => 1, + 'attributes' => [], + ]; + + $this->cart->add($item); + + $this->cart->condition([$itemCondition1, $itemCondition2]); + + // let's prove first we have now two conditions in the cart + expect($this->cart->getConditions()->count())->toEqual(2, 'Cart should have two conditions'); + + // now let's clear cart conditions + $this->cart->clearCartConditions(); + + // cart should have now only 1 condition + expect($this->cart->getConditions()->count())->toEqual(0, 'Cart should have no conditions now'); +}); + +test('get calculated value of a condition', function () { + $this->cart->clear(); + + $cartCondition1 = new CartCondition([ + 'name' => 'SALE 5%', + 'type' => 'sale', + 'target' => 'total', + 'value' => '-5%', + ]); + $cartCondition2 = new CartCondition([ + 'name' => 'Item Gift Pack 25.00', + 'type' => 'promo', + 'target' => 'total', + 'value' => '-25', + ]); + + $item = [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 100, + 'quantity' => 1, + 'attributes' => [], + ]; + + $this->cart->add($item); + + $this->cart->condition([$cartCondition1, $cartCondition2]); + + $subTotal = $this->cart->getSubTotal(); + + expect($subTotal)->toEqual(100, 'Subtotal should be 100'); + + // way 1 + // now we will get the calculated value of the condition 1 + $cond1 = $this->cart->getCondition('SALE 5%'); + expect($cond1->getCalculatedValue($subTotal))->toEqual(5, 'The calculated value must be 5'); + + // way 2 + // get all cart conditions and get their calculated values + $conditions = $this->cart->getConditions(); + expect($conditions['SALE 5%']->getCalculatedValue($subTotal))->toEqual(5, 'First condition calculated value must be 5'); + expect($conditions['Item Gift Pack 25.00']->getCalculatedValue($subTotal))->toEqual(25, 'First condition calculated value must be 5'); +}); + +test('get conditions by type', function () { + $cartCondition1 = new CartCondition([ + 'name' => 'SALE 5%', + 'type' => 'sale', + 'target' => 'total', + 'value' => '-5%', + ]); + $cartCondition2 = new CartCondition([ + 'name' => 'Item Gift Pack 25.00', + 'type' => 'promo', + 'target' => 'total', + 'value' => '-25', + ]); + $cartCondition3 = new CartCondition([ + 'name' => 'Item Less 8%', + 'type' => 'promo', + 'target' => 'total', + 'value' => '-8%', + ]); + + $item = [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 100, + 'quantity' => 1, + 'attributes' => [], + ]; + + $this->cart->add($item); + + $this->cart->condition([$cartCondition1, $cartCondition2, $cartCondition3]); + + // now lets get all conditions added in the cart with the type "promo" + $promoConditions = $this->cart->getConditionsByType('promo'); + + expect($promoConditions->count())->toEqual(2, 'We should have 2 items as promo condition type.'); +}); + +test('remove conditions by type', function () { + // NOTE: + // when add a new condition, the condition's name will be the key to be use + // to access the condition. For some reasons, if the condition name contains + // a "dot" on it ("."), for example adding a condition with name "SALE 35.00" + // this will cause issues when removing this condition by name, this will not be removed + // so when adding a condition, the condition name should not contain any "period" (.) + // to avoid any issues removing it using remove method: removeCartCondition($conditionName); + $cartCondition1 = new CartCondition([ + 'name' => 'SALE 5%', + 'type' => 'sale', + 'target' => 'total', + 'value' => '-5%', + ]); + $cartCondition2 = new CartCondition([ + 'name' => 'Item Gift Pack 20', + 'type' => 'promo', + 'target' => 'total', + 'value' => '-25', + ]); + $cartCondition3 = new CartCondition([ + 'name' => 'Item Less 8%', + 'type' => 'promo', + 'target' => 'total', + 'value' => '-8%', + ]); + + $item = [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 100, + 'quantity' => 1, + 'attributes' => [], + ]; + + $this->cart->add($item); + + $this->cart->condition([$cartCondition1, $cartCondition2, $cartCondition3]); + + // now lets remove all conditions added in the cart with the type "promo" + $this->cart->removeConditionsByType('promo'); + + expect($this->cart->getConditions()->count())->toEqual(1, 'We should have 1 condition remaining as promo conditions type has been removed.'); +}); + +test('add cart condition without condition attributes', function () { + $cartCondition1 = new CartCondition([ + 'name' => 'SALE 5%', + 'type' => 'sale', + 'target' => 'total', + 'value' => '-5%', + ]); + + $item = [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 100, + 'quantity' => 1, + 'attributes' => [], + ]; + + $this->cart->add($item); + + $this->cart->condition([$cartCondition1]); + + // prove first we have now the condition on the cart + $contition = $this->cart->getCondition('SALE 5%'); + expect($contition->getName())->toEqual('SALE 5%'); + + // when get attribute is called and there is no attributes added, + // it should return an empty array + $conditionAttribute = $contition->getAttributes(); + expect($conditionAttribute)->toBeArray(); +}); + +test('add cart condition with condition attributes', function () { + $cartCondition1 = new CartCondition([ + 'name' => 'SALE 5%', + 'type' => 'sale', + 'target' => 'total', + 'value' => '-5%', + 'attributes' => [ + 'description' => 'october fest promo sale', + 'sale_start_date' => '2015-01-20', + 'sale_end_date' => '2015-01-30', + ], + ]); + + $item = [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 100, + 'quantity' => 1, + 'attributes' => [], + ]; + + $this->cart->add($item); + + $this->cart->condition([$cartCondition1]); + + // prove first we have now the condition on the cart + $contition = $this->cart->getCondition('SALE 5%'); + expect($contition->getName())->toEqual('SALE 5%'); + + // when get attribute is called and there is no attributes added, + // it should return an empty array + $conditionAttributes = $contition->getAttributes(); + expect($conditionAttributes)->toBeArray(); + expect($conditionAttributes)->toHaveKey('description'); + expect($conditionAttributes)->toHaveKey('sale_start_date'); + expect($conditionAttributes)->toHaveKey('sale_end_date'); + expect($conditionAttributes['description'])->toEqual('october fest promo sale'); + expect($conditionAttributes['sale_start_date'])->toEqual('2015-01-20'); + expect($conditionAttributes['sale_end_date'])->toEqual('2015-01-30'); +}); + +test('get order from condition', function () { + $cartCondition1 = new CartCondition([ + 'name' => 'SALE 5%', + 'type' => 'sale', + 'target' => 'total', + 'value' => '-5%', + 'order' => 2, + ]); + $cartCondition2 = new CartCondition([ + 'name' => 'Item Gift Pack 20', + 'type' => 'promo', + 'target' => 'total', + 'value' => '-25', + 'order' => '3', + ]); + $cartCondition3 = new CartCondition([ + 'name' => 'Item Less 8%', + 'type' => 'tax', + 'target' => 'total', + 'value' => '-8%', + 'order' => 'first', + ]); + + expect($cartCondition1->getOrder())->toEqual(2); + expect($cartCondition2->getOrder())->toEqual(3); + // numeric string is converted to integer + expect($cartCondition3->getOrder())->toEqual(0); + + // no numeric string is converted to 0 + $this->cart->condition($cartCondition1); + $this->cart->condition($cartCondition2); + $this->cart->condition($cartCondition3); + + $conditions = $this->cart->getConditions(); + + expect($conditions->shift()->getType())->toEqual('sale'); + expect($conditions->shift()->getType())->toEqual('promo'); + expect($conditions->shift()->getType())->toEqual('tax'); +}); + +test('condition ordering', function () { + $cartCondition1 = new CartCondition([ + 'name' => 'TAX', + 'type' => 'tax', + 'target' => 'total', + 'value' => '-8%', + 'order' => 5, + ]); + $cartCondition2 = new CartCondition([ + 'name' => 'SALE 5%', + 'type' => 'sale', + 'target' => 'total', + 'value' => '-5%', + 'order' => 2, + ]); + $cartCondition3 = new CartCondition([ + 'name' => 'Item Gift Pack 20', + 'type' => 'promo', + 'target' => 'total', + 'value' => '-25', + 'order' => 1, + ]); + + $this->cart->condition($cartCondition1); + $this->cart->condition($cartCondition2); + $this->cart->condition($cartCondition3); + + expect($this->cart->getConditions()->first()->getName())->toEqual('Item Gift Pack 20'); + expect($this->cart->getConditions()->last()->getName())->toEqual('TAX'); +}); diff --git a/tests/Unit/CartTest.php b/tests/Unit/CartTest.php new file mode 100644 index 0000000..6f8daf8 --- /dev/null +++ b/tests/Unit/CartTest.php @@ -0,0 +1,531 @@ +shouldReceive('dispatch'); + + $this->cart = new Cart( + new SessionMock, + $events, + 'shopping', + 'SAMPLESESSIONKEY', + require (__DIR__ . '/../Helpers/ConfigMock.php') + ); +}); + +afterEach(function () { + Mockery::close(); +}); + +test('cart can add item', function () { + $this->cart->add(455, 'Sample Item', 100.99, 2, []); + + expect($this->cart->isEmpty())->toBeFalse('Cart should not be empty'); + expect($this->cart->getContent()->count())->toEqual(1, 'Cart content should be 1'); + expect($this->cart->getContent()->first()['id'])->toEqual(455, 'Item added has ID of 455 so first content ID should be 455'); + expect($this->cart->getContent()->first()['price'])->toEqual(100.99, 'Item added has price of 100.99 so first content price should be 100.99'); +}); + +test('cart can add item with no price', function () { + $this->cart->add(455, 'Sample Item', 0, 2, []); + + expect($this->cart->isEmpty())->toBeFalse('Cart should not be empty'); + expect($this->cart->getContent()->count())->toEqual(1, 'Cart content should be 1'); + expect($this->cart->getContent()->first()['id'])->toEqual(455, 'Item added has ID of 455 so first content ID should be 455'); + expect($this->cart->getTotal())->toEqual(0, 'Total should be 0'); +}); + +test('cart can add items as array', function () { + $item = [ + 'id' => 456, + 'name' => 'Sample Item', + 'price' => 67.99, + 'quantity' => 4, + 'attributes' => [], + ]; + + $this->cart->add($item); + + expect($this->cart->isEmpty())->toBeFalse('Cart should not be empty'); + expect($this->cart->getContent()->count())->toEqual(1, 'Cart should have 1 item on it'); + expect($this->cart->getContent()->first()['id'])->toEqual(456, 'The first content must have ID of 456'); + expect($this->cart->getContent()->first()['name'])->toEqual('Sample Item', 'The first content must have name of "Sample Item"'); +}); + +test('cart can add items with multidimensional array', function () { + $items = [ + [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 67.99, + 'quantity' => 4, + 'attributes' => [], + ], + [ + 'id' => 568, + 'name' => 'Sample Item 2', + 'price' => 69.25, + 'quantity' => 4, + 'attributes' => [], + ], + [ + 'id' => 856, + 'name' => 'Sample Item 3', + 'price' => 50.25, + 'quantity' => 4, + 'attributes' => [], + ], + ]; + + $this->cart->add($items); + + expect($this->cart->isEmpty())->toBeFalse('Cart should not be empty'); + expect($this->cart->getContent()->toArray())->toHaveCount(3, 'Cart should have 3 items'); +}); + +test('cart can add item without attributes', function () { + $item = [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 67.99, + 'quantity' => 4, + ]; + + $this->cart->add($item); + + expect($this->cart->isEmpty())->toBeFalse('Cart should not be empty'); +}); + +test('cart update with attribute then attributes should be still instance of item attribute collection', function () { + $item = [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 67.99, + 'quantity' => 4, + 'attributes' => [ + 'product_id' => '145', + 'color' => 'red', + ], + ]; + $this->cart->add($item); + + // lets get the attribute and prove first its an instance of + // ItemAttributeCollection + $item = $this->cart->get(456); + + expect($item->attributes)->toBeInstanceOf('Wearepixel\Cart\ItemAttributeCollection'); + + // now lets update the item with its new attributes + // when we get that item from cart, it should still be an instance of ItemAttributeCollection + $updatedItem = [ + 'attributes' => [ + 'product_id' => '145', + 'color' => 'red', + ], + ]; + $this->cart->update(456, $updatedItem); + + expect($item->attributes)->toBeInstanceOf('Wearepixel\Cart\ItemAttributeCollection'); +}); + +test('cart items attributes', function () { + $item = [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 67.99, + 'quantity' => 4, + 'attributes' => [ + 'size' => 'L', + 'color' => 'blue', + ], + ]; + + $this->cart->add($item); + + expect($this->cart->isEmpty())->toBeFalse('Cart should not be empty'); + expect($this->cart->getContent()->first()['attributes'])->toHaveCount(2, 'Item\'s attribute should have two'); + expect($this->cart->getContent()->first()->attributes->size)->toEqual('L', 'Item should have attribute size of L'); + expect($this->cart->getContent()->first()->attributes->color)->toEqual('blue', 'Item should have attribute color of blue'); + expect($this->cart->get(456)->has('attributes'))->toBeTrue('Item should have attributes'); + expect($this->cart->get(456)->get('attributes')->size)->toEqual('L'); +}); + +test('cart update existing item', function () { + $items = [ + [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 67.99, + 'quantity' => 3, + 'attributes' => [], + ], + [ + 'id' => 568, + 'name' => 'Sample Item 2', + 'price' => 69.25, + 'quantity' => 1, + 'attributes' => [], + ], + ]; + + $this->cart->add($items); + + $itemIdToEvaluate = 456; + + $item = $this->cart->get($itemIdToEvaluate); + expect($item['name'])->toEqual('Sample Item 1', 'Item name should be "Sample Item 1"'); + expect($item['price'])->toEqual(67.99, 'Item price should be "67.99"'); + expect($item['quantity'])->toEqual(3, 'Item quantity should be 3'); + + // when cart's item quantity is updated, the subtotal should be updated as well + $this->cart->update(456, [ + 'name' => 'Renamed', + 'quantity' => 2, + 'price' => 105, + ]); + + $item = $this->cart->get($itemIdToEvaluate); + expect($item['name'])->toEqual('Renamed', 'Item name should be "Renamed"'); + expect($item['price'])->toEqual(105, 'Item price should be 105'); + expect($item['quantity'])->toEqual(5, 'Item quantity should be 2'); +}); + +test('cart update existing item with quantity as array and not relative', function () { + $items = [ + [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 67.99, + 'quantity' => 3, + 'attributes' => [], + ], + ]; + + $this->cart->add($items); + + $itemIdToEvaluate = 456; + $item = $this->cart->get($itemIdToEvaluate); + expect($item['quantity'])->toEqual(3, 'Item quantity should be 3'); + + // now by default when an update takes place and the quantity attribute + // is present, it will evaluate for arithmetic operation if the quantity + // should be incremented or decremented, we should also allow the quantity + // value to be in array format and provide a field if the quantity should not be + // treated as relative to Item quantity current value + $this->cart->update($itemIdToEvaluate, ['quantity' => ['relative' => false, 'value' => 5]]); + + $item = $this->cart->get($itemIdToEvaluate); + expect($item['quantity'])->toEqual(5, 'Item quantity should be 5'); +}); + +test('item price should be normalized when added to cart', function () { + // add a price in a string format should be converted to float + $this->cart->add(455, 'Sample Item', '100.99', 2, []); + + expect($this->cart->getContent()->first()['price'])->toBeFloat('Cart price should be a float'); +}); + +test('it removes an item on cart by item id', function () { + $items = [ + [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 67.99, + 'quantity' => 4, + 'attributes' => [], + ], + [ + 'id' => 568, + 'name' => 'Sample Item 2', + 'price' => 69.25, + 'quantity' => 4, + 'attributes' => [], + ], + [ + 'id' => 856, + 'name' => 'Sample Item 3', + 'price' => 50.25, + 'quantity' => 4, + 'attributes' => [], + ], + ]; + + $this->cart->add($items); + + $removeItemId = 456; + + $this->cart->remove($removeItemId); + + expect($this->cart->getContent()->toArray())->toHaveCount(2, 'Cart must have 2 items left'); + expect($this->cart->getContent()->has($removeItemId))->toBeFalse('Cart must have not contain the remove item anymore'); +}); + +test('cart sub total', function () { + $items = [ + [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 67.99, + 'quantity' => 1, + 'attributes' => [], + ], + [ + 'id' => 568, + 'name' => 'Sample Item 2', + 'price' => 69.25, + 'quantity' => 1, + 'attributes' => [], + ], + [ + 'id' => 856, + 'name' => 'Sample Item 3', + 'price' => 50.25, + 'quantity' => 1, + 'attributes' => [], + ], + ]; + + $this->cart->add($items); + + expect($this->cart->getSubTotal())->toEqual(187.49, 'Cart should have sub total of 187.49'); + + // if we remove an item, the sub total should be updated as well + $this->cart->remove(456); + + expect($this->cart->getSubTotal())->toEqual(119.5, 'Cart should have sub total of 119.5'); +}); + +test('sub total when item quantity is updated', function () { + $this->cart->clear(); + + $items = [ + [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 67.99, + 'quantity' => 3, + 'attributes' => [], + ], + [ + 'id' => 568, + 'name' => 'Sample Item 2', + 'price' => 69.25, + 'quantity' => 1, + 'attributes' => [], + ], + ]; + + $this->cart->add($items); + + expect($this->cart->getSubTotal()) + ->toBe(273.22, 'Cart should have sub total of 273.22'); + + // when cart's item quantity is updated, the subtotal should be updated as well + $this->cart->update(456, ['quantity' => 2]); + + expect($this->cart->getSubTotal())->toEqual(409.2, 'Cart should have sub total of 409.2'); +}); + +test('sub total when item quantity is updated by reduced', function () { + // $this->cart->setConfig(['format_numbers' => true, 'decimals' => 2, 'dec_point' => '.', 'thousands_sep' => ',']); + + $items = [ + [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 67.99, + 'quantity' => 3, + 'attributes' => [], + ], + [ + 'id' => 568, + 'name' => 'Sample Item 2', + 'price' => 69.25, + 'quantity' => 1, + 'attributes' => [], + ], + ]; + + $this->cart->add($items); + + expect($this->cart->getSubTotal()) + ->toBe(273.22, 'Cart should have sub total of 273.22'); + + // when cart's item quantity is updated, the subtotal should be updated as well + $this->cart->update(456, ['quantity' => -1]); + + // get the item to be evaluated + $item = $this->cart->get(456); + + expect($item['quantity'])->toEqual(2, 'Item quantity of with item ID of 456 should now be reduced to 2'); + expect($this->cart->getSubTotal())->toEqual(205.23, 'Cart should have sub total of 205.23'); +}); + +test('item quantity update by reduced should not reduce if quantity will result to zero', function () { + $items = [ + [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 67.99, + 'quantity' => 3, + 'attributes' => [], + ], + [ + 'id' => 568, + 'name' => 'Sample Item 2', + 'price' => 69.25, + 'quantity' => 1, + 'attributes' => [], + ], + ]; + + $this->cart->add($items); + + // get the item to be evaluated + $item = $this->cart->get(456); + + // prove first we have quantity of 3 + expect($item['quantity'])->toEqual(3, 'Item quantity of with item ID of 456 should be reduced to 3'); + + // when cart's item quantity is updated, and reduced to more than the current quantity + // this should not work + $this->cart->update(456, ['quantity' => -3]); + + expect($item['quantity'])->toEqual(3, 'Item quantity of with item ID of 456 should now be reduced to 2'); +}); + +test('should throw exception when provided invalid values scenario one', function () { + $this->expectException('Wearepixel\Cart\Exceptions\InvalidItemException'); + $this->cart->add(455, 'Sample Item', 100.99, 0, []); +}); + +test('should throw exception when provided invalid values scenario two', function () { + $this->expectException('Wearepixel\Cart\Exceptions\InvalidItemException'); + $this->cart->add('', 'Sample Item', 100.99, 2, []); +}); + +test('should throw exception when provided invalid values scenario three', function () { + $this->expectException('Wearepixel\Cart\Exceptions\InvalidItemException'); + $this->cart->add(523, '', 100.99, 2, []); +}); + +test('clearing cart', function () { + $items = [ + [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 67.99, + 'quantity' => 3, + 'attributes' => [], + ], + [ + 'id' => 568, + 'name' => 'Sample Item 2', + 'price' => 69.25, + 'quantity' => 1, + 'attributes' => [], + ], + ]; + + $this->cart->add($items); + + expect($this->cart->isEmpty())->toBeFalse('prove first cart is not empty'); + + // now let's clear cart + $this->cart->clear(); + + expect($this->cart->isEmpty())->toBeTrue('cart should now be empty'); +}); + +test('cart get total quantity', function () { + $items = [ + [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 67.99, + 'quantity' => 3, + 'attributes' => [], + ], + [ + 'id' => 568, + 'name' => 'Sample Item 2', + 'price' => 69.25, + 'quantity' => 1, + 'attributes' => [], + ], + ]; + + $this->cart->add($items); + + expect($this->cart->isEmpty())->toBeFalse('prove first cart is not empty'); + + // now let's count the cart's quantity + expect($this->cart->getTotalQuantity())->toBeInt('Return type should be INT'); + expect($this->cart->getTotalQuantity())->toEqual(4, 'Cart\'s quantity should be 4.'); +}); + +test('cart can add items as array with associated model', function () { + $item = [ + 'id' => 456, + 'name' => 'Sample Item', + 'price' => 67.99, + 'quantity' => 4, + 'attributes' => [], + 'associatedModel' => MockProduct::class, + ]; + + $this->cart->add($item); + + $addedItem = $this->cart->get($item['id']); + + expect($this->cart->isEmpty())->toBeFalse('Cart should not be empty'); + expect($this->cart->getContent()->count())->toEqual(1, 'Cart should have 1 item on it'); + expect($this->cart->getContent()->first()['id'])->toEqual(456, 'The first content must have ID of 456'); + expect($this->cart->getContent()->first()['name'])->toEqual('Sample Item', 'The first content must have name of "Sample Item"'); + expect($addedItem->model)->toBeInstanceOf('Wearepixel\Cart\Tests\Helpers\MockProduct'); +}); + +test('cart can add items with multidimensional array with associated model', function () { + $items = [ + [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 67.99, + 'quantity' => 4, + 'attributes' => [], + 'associatedModel' => MockProduct::class, + ], + [ + 'id' => 568, + 'name' => 'Sample Item 2', + 'price' => 69.25, + 'quantity' => 4, + 'attributes' => [], + 'associatedModel' => MockProduct::class, + ], + [ + 'id' => 856, + 'name' => 'Sample Item 3', + 'price' => 50.25, + 'quantity' => 4, + 'attributes' => [], + 'associatedModel' => MockProduct::class, + ], + ]; + + $this->cart->add($items); + + $content = $this->cart->getContent(); + foreach ($content as $item) { + expect($item->model)->toBeInstanceOf('Wearepixel\Cart\Tests\Helpers\MockProduct'); + } + + expect($this->cart->isEmpty())->toBeFalse('Cart should not be empty'); + expect($this->cart->getContent()->toArray())->toHaveCount(3, 'Cart should have 3 items'); + expect($this->cart->getTotalQuantity())->toBeInt('Return type should be INT'); + expect($this->cart->getTotalQuantity())->toEqual(12, 'Cart\'s quantity should be 4.'); +}); diff --git a/tests/Unit/CartTestEventsTest.php b/tests/Unit/CartTestEventsTest.php new file mode 100644 index 0000000..d098ea0 --- /dev/null +++ b/tests/Unit/CartTestEventsTest.php @@ -0,0 +1,201 @@ +shouldReceive('dispatch')->once()->with('cart.created', Mockery::type('array'), true); + + $cart = new Cart( + new SessionMock, + $events, + 'cart', + 'SAMPLESESSIONKEY', + require (__DIR__ . '/../Helpers/ConfigMock.php') + ); + + expect(true)->toBeTrue(); +}); + +test('event cart adding', function () { + $events = Mockery::mock('Illuminate\Events\Dispatcher'); + $events->shouldReceive('dispatch')->once()->with('cart.created', Mockery::type('array'), true); + $events->shouldReceive('dispatch')->once()->with('cart.adding', Mockery::type('array'), true); + $events->shouldReceive('dispatch')->once()->with('cart.added', Mockery::type('array'), true); + + $cart = new Cart( + new SessionMock, + $events, + 'cart', + 'SAMPLESESSIONKEY', + require (__DIR__ . '/../Helpers/ConfigMock.php') + ); + + $cart->add(455, 'Sample Item', 100.99, 2, []); + + expect(true)->toBeTrue(); +}); + +test('event cart adding multiple times', function () { + $events = Mockery::mock('Illuminate\Events\Dispatcher'); + $events->shouldReceive('dispatch')->once()->with('cart.created', Mockery::type('array'), true); + $events->shouldReceive('dispatch')->times(2)->with('cart.adding', Mockery::type('array'), true); + $events->shouldReceive('dispatch')->times(2)->with('cart.added', Mockery::type('array'), true); + + $cart = new Cart( + new SessionMock, + $events, + 'cart', + 'SAMPLESESSIONKEY', + require (__DIR__ . '/../Helpers/ConfigMock.php') + ); + + $cart->add(455, 'Sample Item 1', 100.99, 2, []); + $cart->add(562, 'Sample Item 2', 100.99, 2, []); + + expect(true)->toBeTrue(); +}); + +test('event cart adding multiple times scenario two', function () { + $events = Mockery::mock('Illuminate\Events\Dispatcher'); + $events->shouldReceive('dispatch')->once()->with('cart.created', Mockery::type('array'), true); + $events->shouldReceive('dispatch')->times(3)->with('cart.adding', Mockery::type('array'), true); + $events->shouldReceive('dispatch')->times(3)->with('cart.added', Mockery::type('array'), true); + + $items = [ + [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 67.99, + 'quantity' => 4, + 'attributes' => [], + ], + [ + 'id' => 568, + 'name' => 'Sample Item 2', + 'price' => 69.25, + 'quantity' => 4, + 'attributes' => [], + ], + [ + 'id' => 856, + 'name' => 'Sample Item 3', + 'price' => 50.25, + 'quantity' => 4, + 'attributes' => [], + ], + ]; + + $cart = new Cart( + new SessionMock, + $events, + 'cart', + 'SAMPLESESSIONKEY', + require (__DIR__ . '/../Helpers/ConfigMock.php') + ); + + $cart->add($items); + + expect(true)->toBeTrue(); +}); + +test('event cart remove item', function () { + $events = Mockery::mock('Illuminate\Events\Dispatcher'); + $events->shouldReceive('dispatch')->once()->with('cart.created', Mockery::type('array'), true); + $events->shouldReceive('dispatch')->times(3)->with('cart.adding', Mockery::type('array'), true); + $events->shouldReceive('dispatch')->times(3)->with('cart.added', Mockery::type('array'), true); + $events->shouldReceive('dispatch')->times(1)->with('cart.removing', Mockery::type('array'), true); + $events->shouldReceive('dispatch')->times(1)->with('cart.removed', Mockery::type('array'), true); + + $items = [ + [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 67.99, + 'quantity' => 4, + 'attributes' => [], + ], + [ + 'id' => 568, + 'name' => 'Sample Item 2', + 'price' => 69.25, + 'quantity' => 4, + 'attributes' => [], + ], + [ + 'id' => 856, + 'name' => 'Sample Item 3', + 'price' => 50.25, + 'quantity' => 4, + 'attributes' => [], + ], + ]; + + $cart = new Cart( + new SessionMock, + $events, + 'cart', + 'SAMPLESESSIONKEY', + require (__DIR__ . '/../Helpers/ConfigMock.php') + ); + + $cart->add($items); + + $cart->remove(456); + + expect(true)->toBeTrue(); +}); + +test('event cart clear', function () { + $events = Mockery::mock('Illuminate\Events\Dispatcher'); + $events->shouldReceive('dispatch')->once()->with('cart.created', Mockery::type('array'), true); + $events->shouldReceive('dispatch')->times(3)->with('cart.adding', Mockery::type('array'), true); + $events->shouldReceive('dispatch')->times(3)->with('cart.added', Mockery::type('array'), true); + $events->shouldReceive('dispatch')->once()->with('cart.clearing', Mockery::type('array'), true); + $events->shouldReceive('dispatch')->once()->with('cart.cleared', Mockery::type('array'), true); + + $items = [ + [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 67.99, + 'quantity' => 4, + 'attributes' => [], + ], + [ + 'id' => 568, + 'name' => 'Sample Item 2', + 'price' => 69.25, + 'quantity' => 4, + 'attributes' => [], + ], + [ + 'id' => 856, + 'name' => 'Sample Item 3', + 'price' => 50.25, + 'quantity' => 4, + 'attributes' => [], + ], + ]; + + $cart = new Cart( + new SessionMock, + $events, + 'cart', + 'SAMPLESESSIONKEY', + require (__DIR__ . '/../Helpers/ConfigMock.php') + ); + + $cart->add($items); + + $cart->clear(); + + expect(true)->toBeTrue(); +}); diff --git a/tests/Unit/CartTestMultipleInstancesTest.php b/tests/Unit/CartTestMultipleInstancesTest.php new file mode 100644 index 0000000..eb1017c --- /dev/null +++ b/tests/Unit/CartTestMultipleInstancesTest.php @@ -0,0 +1,79 @@ +shouldReceive('dispatch'); + + $this->cart1 = new Cart( + new SessionMock, + $events, + 'shopping', + 'uniquesessionkey123', + require (__DIR__ . '/../Helpers/ConfigMock.php') + ); + + $this->cart2 = new Cart( + new SessionMock, + $events, + 'wishlist', + 'uniquesessionkey456', + require (__DIR__ . '/../Helpers/ConfigMock.php') + ); +}); + +afterEach(function () { + Mockery::close(); +}); + +test('cart multiple instances', function () { + // add 3 items on cart 1 + $itemsForCart1 = [ + [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 67.99, + 'quantity' => 4, + 'attributes' => [], + ], + [ + 'id' => 568, + 'name' => 'Sample Item 2', + 'price' => 69.25, + 'quantity' => 4, + 'attributes' => [], + ], + [ + 'id' => 856, + 'name' => 'Sample Item 3', + 'price' => 50.25, + 'quantity' => 4, + 'attributes' => [], + ], + ]; + + $this->cart1->add($itemsForCart1); + + expect($this->cart1->isEmpty())->toBeFalse('Cart should not be empty'); + expect($this->cart1->getContent()->toArray())->toHaveCount(3, 'Cart should have 3 items'); + expect($this->cart1->getInstanceName())->toEqual('shopping', 'Cart 1 should have instance name of "shopping"'); + + // add 1 item on cart 2 + $itemsForCart2 = [ + [ + 'id' => 456, + 'name' => 'Sample Item 1', + 'price' => 67.99, + 'quantity' => 4, + 'attributes' => [], + ], + ]; + + $this->cart2->add($itemsForCart2); + + expect($this->cart2->isEmpty())->toBeFalse('Cart should not be empty'); + expect($this->cart2->getContent()->toArray())->toHaveCount(1, 'Cart should have 3 items'); + expect($this->cart2->getInstanceName())->toEqual('wishlist', 'Cart 2 should have instance name of "wishlist"'); +}); diff --git a/tests/Unit/ItemTest.php b/tests/Unit/ItemTest.php new file mode 100644 index 0000000..fd18d39 --- /dev/null +++ b/tests/Unit/ItemTest.php @@ -0,0 +1,102 @@ +shouldReceive('dispatch'); + + $this->cart = new Cart( + new SessionMock, + $events, + 'shopping', + 'SAMPLESESSIONKEY', + require (__DIR__ . '/../Helpers/ConfigMock.php') + ); +}); + +afterEach(function () { + Mockery::close(); +}); + +test('item get sum price using property', function () { + $this->cart->add(455, 'Sample Item', 100.99, 2, []); + + $item = $this->cart->get(455); + + expect($item->getPriceSum())->toEqual(201.98, 'Item summed price should be 201.98'); +}); + +test('item get sum price using array style', function () { + $this->cart->add(455, 'Sample Item', 100.99, 2, []); + + $item = $this->cart->get(455); + + expect($item->getPriceSum())->toEqual(201.98, 'Item summed price should be 201.98'); +}); + +test('item get conditions empty', function () { + $this->cart->add(455, 'Sample Item', 100.99, 2, []); + + $item = $this->cart->get(455); + + expect($item->getConditions())->toBeEmpty('Item should have no conditions'); +}); + +test('item get conditions with conditions', function () { + $itemCondition1 = new \Wearepixel\Cart\CartCondition([ + 'name' => 'SALE 5%', + 'type' => 'sale', + 'target' => 'item', + 'value' => '-5%', + ]); + + $itemCondition2 = new CartCondition([ + 'name' => 'Item Gift Pack 25.00', + 'type' => 'promo', + 'target' => 'item', + 'value' => '-25', + ]); + + $this->cart->add(455, 'Sample Item', 100.99, 2, [], [$itemCondition1, $itemCondition2]); + + $item = $this->cart->get(455); + + expect($item->getConditions())->toHaveCount(2, 'Item should have two conditions'); +}); + +test('item associate model', function () { + $this->cart->add(455, 'Sample Item', 100.99, 2, [])->associate(MockProduct::class); + + $item = $this->cart->get(455); + + expect($item->associatedModel)->toEqual(MockProduct::class, 'Item assocaited model should be ' . MockProduct::class); +}); + +test('it will throw an exception when a non existing model is being associated', function () { + $this->expectException(\Wearepixel\Cart\Exceptions\UnknownModelException::class); + $this->expectExceptionMessage('The supplied model SomeModel does not exist.'); + + $this->cart->add(1, 'Test item', 1, 10.00)->associate('SomeModel'); +}); + +test('item get model', function () { + $this->cart->add(455, 'Sample Item', 100.99, 2, [])->associate(MockProduct::class); + + $item = $this->cart->get(455); + + expect($item->model)->toBeInstanceOf(MockProduct::class); + expect($item->model->name)->toEqual('Sample Item'); + expect($item->model->id)->toEqual(455); +}); + +test('item get model will return null if it has no model', function () { + $this->cart->add(455, 'Sample Item', 100.99, 2, []); + + $item = $this->cart->get(455); + + expect($item->model)->toEqual(null); +}); diff --git a/tests/Unit/ItemTestOtherFormatTest.php b/tests/Unit/ItemTestOtherFormatTest.php new file mode 100644 index 0000000..928e8af --- /dev/null +++ b/tests/Unit/ItemTestOtherFormatTest.php @@ -0,0 +1,37 @@ +shouldReceive('dispatch'); + + $this->cart = new Cart( + new SessionMock, + $events, + 'shopping', + 'SAMPLESESSIONKEY', + require (__DIR__ . '/../Helpers/ConfigMockOtherFormat.php') + ); +}); + +afterEach(function () { + Mockery::close(); +}); + +test('item get sum price using property', function () { + $this->cart->add(455, 'Sample Item', 100.99, 2, []); + + $item = $this->cart->get(455); + + expect($item->getPriceSum())->toEqual('201.98', 'Item summed price should be 201.98'); +}); + +test('item get sum price using array style', function () { + $this->cart->add(455, 'Sample Item', 100.99, 2, []); + + $item = $this->cart->get(455); + + expect($item->getPriceSum())->toEqual('201.98', 'Item summed price should be 201.98'); +});