From 9e71108bac929f71d2f74211555033f778b86f09 Mon Sep 17 00:00:00 2001 From: Jeremy Wadhams Date: Wed, 12 Jul 2023 15:02:23 -0500 Subject: [PATCH 1/2] Initial Commit --- .gitignore | 2 + .prettierrc | 8 + README.md | 14 +- app/CollectionOfJsonModels.php | 311 + app/Contracts/CanCascadeEvents.php | 28 + app/Exceptions/UniqueException.php | 25 + app/Helpers/ClassUsesTrait.php | 17 + app/Helpers/FriendlyClassName.php | 37 + app/Helpers/Json.php | 145 + app/JsonModel.php | 591 ++ app/Traits/HasJsonModelAttributes.php | 362 + app/Traits/HasLinkedData.php | 127 + .../NullWhenUsedAsAttributeWhenEmpty.php | 41 + composer.json | 46 + composer.lock | 8558 +++++++++++++++++ .../Tests/Mocks/Models/VehicleFactory.php | 18 + phpunit.xml | 21 + tests/BaseTestCase.php | 55 + tests/CustomMockInterface.php | 23 + tests/MockClasses/ConcreteJsonModel.php | 20 + .../MockClasses/ConcreteNullableJsonModel.php | 21 + tests/MockClasses/EventedJsonModel.php | 74 + .../EventedModelWithJsonAttributes.php | 94 + tests/MockClasses/Invokeable.php | 26 + tests/Mocks/Models/Vehicle.php | 13 + tests/Schemas/loose_json_model.json | 6 + tests/Schemas/person.json | 24 + tests/Schemas/vehicle.json | 12 + tests/Schemas/vin.json | 9 + tests/Unit/CollectionOfJsonModelsTest.php | 496 + tests/Unit/JsonModelTest.php | 861 ++ .../Traits/HasJsonModelAttributesTest.php | 425 + tests/bootstrap.php | 106 + 33 files changed, 12614 insertions(+), 2 deletions(-) create mode 100644 .prettierrc create mode 100644 app/CollectionOfJsonModels.php create mode 100644 app/Contracts/CanCascadeEvents.php create mode 100644 app/Exceptions/UniqueException.php create mode 100644 app/Helpers/ClassUsesTrait.php create mode 100644 app/Helpers/FriendlyClassName.php create mode 100644 app/Helpers/Json.php create mode 100644 app/JsonModel.php create mode 100644 app/Traits/HasJsonModelAttributes.php create mode 100644 app/Traits/HasLinkedData.php create mode 100644 app/Traits/NullWhenUsedAsAttributeWhenEmpty.php create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 database/Factories/Tests/Mocks/Models/VehicleFactory.php create mode 100644 phpunit.xml create mode 100644 tests/BaseTestCase.php create mode 100644 tests/CustomMockInterface.php create mode 100644 tests/MockClasses/ConcreteJsonModel.php create mode 100644 tests/MockClasses/ConcreteNullableJsonModel.php create mode 100644 tests/MockClasses/EventedJsonModel.php create mode 100644 tests/MockClasses/EventedModelWithJsonAttributes.php create mode 100644 tests/MockClasses/Invokeable.php create mode 100644 tests/Mocks/Models/Vehicle.php create mode 100644 tests/Schemas/loose_json_model.json create mode 100644 tests/Schemas/person.json create mode 100644 tests/Schemas/vehicle.json create mode 100644 tests/Schemas/vin.json create mode 100644 tests/Unit/CollectionOfJsonModelsTest.php create mode 100644 tests/Unit/JsonModelTest.php create mode 100644 tests/Unit/Traits/HasJsonModelAttributesTest.php create mode 100644 tests/bootstrap.php diff --git a/.gitignore b/.gitignore index 297959a..89543e4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ node_modules/ npm-debug.log yarn-error.log +.idea/ +.ackrc # Laravel 4 specific bootstrap/compiled.php diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..c589811 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "tabWidth": 4, + "printWidth": 120, + "singleQuote": true, + "bracketSpacing": true, + "trailingComma": "es5", + "phpVersion": "8.0" +} diff --git a/README.md b/README.md index f89957d..2c393df 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,12 @@ -# laravel-json-model -Json-backed models for Laravel +# Laravel Json Model + +We really love Laravel as an ORM. But we have a part of our application that is not backed by a document store, +not a relational database. Json Models let us use the best parts of the Eloquent Models, +but instead of being backed by a row in a table, they're always serialized to JSON. (Which can include +being serialized to an attribute on a traditional Laravel Model!) + +## Setup + +For now, you will need to add the following for events to work properly. +1. In AppServiceProvider boot method: `JsonModel::setEventDispatcher($this->app['events']);` +2. In AppServiceProvider register method: `JsonModel::clearBootedModels();` diff --git a/app/CollectionOfJsonModels.php b/app/CollectionOfJsonModels.php new file mode 100644 index 0000000..9b3a05f --- /dev/null +++ b/app/CollectionOfJsonModels.php @@ -0,0 +1,311 @@ +fresh() */ + protected $itemClass = ''; + + /** @var null|string Optional name of a primary key. If present, you can use ->find and ->push will overwrite existing */ + protected $primaryKey = null; + /** + * This call is mandatory for any real use of this class + * But if you modify the constructor, you start getting failures + * in Collection methods that are written to return `new static` + * @param string $itemClass + * @return CollectionOfJsonModels + */ + public function setType(string $itemClass = null): self + { + if (!is_a($itemClass, JsonModel::class, true)) { + throw new \DomainException('CollectionOfJsonModels type must be a descendent of JsonModel'); + } + $this->itemClass = $itemClass; + + return $this; + } + + /** + * @param string|null $key + * @return CollectionOfJsonModels + */ + public function setPrimaryKey(string $key = null): self + { + $this->primaryKey = $key; + return $this; + } + + /** + * Pull data from the link, and fill items with hydrated, linked JsonModels + * @return self + */ + public function fresh(): self + { + return $this->fill($this->getLinkedData() ?: []); + } + + /** + * @param iterable $items + * @return CollectionOfJsonModels + * @throws DomainException + */ + public function fill(iterable $items): self + { + if (!$this->itemClass) { + throw new DomainException("Can't load CollectionOfJsonModels until type has been set."); + } + + $this->items = []; + + foreach ($items as $idx => $item) { + if (!($item instanceof $this->itemClass)) { + $item = new $this->itemClass($item); + } + $item->exists = true; + if ($this->primaryKey) { + $this->items[$item[$this->primaryKey]] = $item; + } else { + $this->items[] = $item; + } + } + $this->reindexItemLinks(); + + return $this; + } + + /** + * Link all items individually with correct numeric indices + */ + protected function reindexItemLinks(): void + { + if (!$this->isLinked()) { + return; + } + foreach ($this->items as $idx => $item) { + $item->link( + $this->upstream_model, + $this->upstream_attribute, + $this->upstream_key ? "{$this->upstream_key}.{$idx}" : "{$idx}", + ); + } + } + + /** + * Push an item onto the end of the Collection. + * Our implementation uses offsetSet (like Laravel 5) to get casting and unique primary keys + * @param mixed $values [optional] + * @return CollectionOfJsonModels + */ + public function push(...$values) + { + foreach ($values as $value) { + $this->offsetSet(null, $value); + } + return $this; + } + + /** + * Override the ArrayAccess method for setting (offsetSet) to: + * cast incoming items (especially arrays) to itemClass + * Check that you're not duplicating primaryKey + * (on implementations that require one) + * @param $key + * @param mixed $value + * @return void + */ + public function offsetSet($key, $value): void + { + if (!$this->itemClass) { + throw new \DomainException("Can't add items to CollectionOfJsonModels until type has been set."); + } + if (!$value instanceof $this->itemClass) { + if (!is_array($value)) { + $differentObject = is_object($value) ? get_class($value) : gettype($value); + throw new \DomainException("Can't insert a {$differentObject} in a Collection of {$this->itemClass}"); + } + + $value = new $this->itemClass($value); + } + + // If ->push (key null) and has primaryKey, make sure you're not duplicating + if ($key === null && $this->primaryKey) { + if (isset($this->items[$value[$this->primaryKey]])) { + throw new UniqueException( + "Collection can't contain duplicate {$this->primaryKey} {$value[$this->primaryKey]}", + ); + } + $this->items[$value[$this->primaryKey]] = $value; + } else { + parent::offsetSet($key, $value); + } + $this->reindexItemLinks(); + } + + /** + * Save the data over the link + * @return bool + */ + public function save(): bool + { + if ($this->preSave() === false) { + return false; + } + + $this->setLinkedData(); + $saved = $this->upstream_model->save(); + + if ($saved) { + $this->postSave(); + } + + return $saved; + } + + /** + * Does this object pass its own standard for validation? + * @return true + * @throws JsonSchemaValidationException if data is invalid + */ + public function validateOrThrow( + string $exceptionMessage = null, + int $failureHttpStatusCode = Response::HTTP_BAD_REQUEST, + ): bool { + if (!$this->itemClass) { + throw new \DomainException("Can't validate a CollectionOfJsonModels until type has been set."); + } + $absoluteCollectionSchemaUri = SchemaValidator::registerRawSchema( + json_encode([ + 'type' => 'array', + 'items' => [ + '$ref' => $this->itemClass::SCHEMA, + ], + ]), + ); + + return SchemaValidator::validateOrThrow( + $this, + $absoluteCollectionSchemaUri, + (new FriendlyClassName())(static::class) . ' contains invalid data!', + failureHttpStatusCode: $failureHttpStatusCode, + ); + } + + /** + * Convert the object into something JSON serializable. + * When we serialize as numeric for the wire, + * But when we're going to disk we serialize it as an object keyed by primary key + * + * @return array + */ + public function jsonSerialize(): array + { + $items_was = $this->items; + $this->items = array_values($this->items); + $serializedNumeric = parent::jsonSerialize(); + $this->items = $items_was; + return $serializedNumeric; + } + + /** + * Given a value, return the first element where primaryKey is that value + * or null if not found + * @param mixed $value + * @return JsonModel|null + */ + public function find($value): ?JsonModel + { + if (!$this->primaryKey) { + throw new \DomainException('Cannot use method find until primary key has been set.'); + } + return $this[$value] ?? null; + } + + /** + * Given a value, return the first element where primaryKey is that value + * @throws ModelNotFoundException if no element is found + * @param $value + * @return JsonModel + */ + public function findOrFail($value): JsonModel + { + $found = $this->find($value); + if (!$found) { + throw (new ModelNotFoundException())->setModel($this->itemClass ?: JsonModel::class); + } + return $found; + } + + /** + * Call preSave observers on all items + * If *any* item returns false, exit early returning false. + * Otherwise return true + * @return bool + */ + public function preSave(): bool + { + return $this->every->preSave(); + } + + /** + * Call postSave observers on all items. Responses are ignored. + */ + public function postSave(): void + { + $this->each->postSave(); + } + + /** + * If you try to buildExpandedObject a CollectionOfJsonModels, + * make sure the children support it, + * then expand all the children with the requested attributes. + * @param array $with + * @return CollectionOfJsonModels + */ + public function buildExpandedObject(array $with): self + { + if (!method_exists($this->itemClass, 'buildExpandedObject')) { + throw new \DomainException( + (new FriendlyClassName())($this) . + ' cannot buildExpandedObject because ' . + (new FriendlyClassName())($this->itemClass) . + ' does not support it.', + ); + } + foreach ($this->items as $item) { + $item->buildExpandedObject($with); + } + return $this; + } +} diff --git a/app/Contracts/CanCascadeEvents.php b/app/Contracts/CanCascadeEvents.php new file mode 100644 index 0000000..3bd709f --- /dev/null +++ b/app/Contracts/CanCascadeEvents.php @@ -0,0 +1,28 @@ +getModel()) + * strip off the name space, and turn the camel case into separated words. + */ + +declare(strict_types=0); + +namespace Carsdotcom\LaravelJsonModel\Helpers; + +class FriendlyClassName +{ + public function __invoke(string|object $class): string + { + $class = is_object($class) ? get_class($class) : $class; + + if (str_contains($class, '@anonymous')) { + $parent = get_parent_class($class); + if (!$parent || $parent === 'stdClass') { + return 'Anonymous Class'; + } + return 'Anonymous Descendent of ' . (new self())($parent); + } + + // Get just the class name (strip namespace) + $className = class_basename($class); + + // Replace punctuation with spaces (especially _ ) + $className = preg_replace('/[^a-z]+/i', ' ', $className); + + // Insert spaces in camel case names + $className = preg_replace('/([a-z])([A-Z])/', '$1 $2', $className); + + return $className; + } +} diff --git a/app/Helpers/Json.php b/app/Helpers/Json.php new file mode 100644 index 0000000..94ee795 --- /dev/null +++ b/app/Helpers/Json.php @@ -0,0 +1,145 @@ + 0) { + $json = (array) $json; + } else { + return; + } + } + + ksort($json); + + foreach ($json as $key => &$value) { + self::recursiveSort($value); + } + } + + /** + * Wrapper for json_decode that throws when an error occurs. + * Cloned here from Guzzle, to make it obvious that it behaves differently from the builtin language function. + * + * @param string $json JSON data to parse + * @param bool $assoc When true, returned objects will be converted + * into associative arrays. + * @param int $depth User specified recursion depth. + * @param int $options Bitmask of JSON decode options. + * + * @return mixed + * @throws InvalidArgumentException if the JSON cannot be decoded. + * @link http://www.php.net/manual/en/function.json-decode.php + */ + public static function decodeOrThrow($json, $assoc = false, $depth = 512, $options = 0) + { + $data = json_decode($json, $assoc, $depth, $options); + if (JSON_ERROR_NONE !== json_last_error()) { + throw new InvalidArgumentException('json_decode error: ' . json_last_error_msg()); + } + + return $data; + } +} diff --git a/app/JsonModel.php b/app/JsonModel.php new file mode 100644 index 0000000..260dcd2 --- /dev/null +++ b/app/JsonModel.php @@ -0,0 +1,591 @@ +link method a triplet of: + * Model + * Attribute on model (typically a cast-JSON column) + * (optional) string key on attribute + */ + +declare(strict_types=1); + +namespace Carsdotcom\LaravelJsonModel; + +use ArrayAccess; +use Carsdotcom\JsonSchemaValidation\Exceptions\JsonSchemaValidationException; +use Carsdotcom\JsonSchemaValidation\Traits\ValidatesWithJsonSchema; +use Carsdotcom\LaravelJsonModel\Contracts\CanCascadeEvents; +use Carsdotcom\JsonSchemaValidation\Contracts\CanValidate; +use Carsdotcom\LaravelJsonModel\Helpers\ClassUsesTrait; +use Carsdotcom\LaravelJsonModel\Helpers\Json; +use Carsdotcom\LaravelJsonModel\Traits\HasJsonModelAttributes; +use Carsdotcom\LaravelJsonModel\Traits\HasLinkedData; +use Illuminate\Contracts\Support\Jsonable; +use Illuminate\Database\Eloquent\Concerns\HasAttributes; +use Illuminate\Database\Eloquent\Concerns\HasEvents; +use Illuminate\Database\Eloquent\Concerns\HasRelationships; +use Illuminate\Database\Eloquent\Concerns\HidesAttributes; +use Illuminate\Database\Eloquent\JsonEncodingException; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Collection; +use JsonSerializable; + +/** + * Class JsonModel + * @package Carsdotcom\LaravelJsonModel + */ +abstract class JsonModel implements ArrayAccess, Jsonable, JsonSerializable, CanValidate, CanCascadeEvents +{ + use HasAttributes; + use HasEvents; + use HasLinkedData; + use HasRelationships; + use HidesAttributes; + use ValidatesWithJsonSchema; + + /** @var string|null JsonSchema used to validate this model before saving. URI or Json string literal */ + public const SCHEMA = null; + + /** @var bool Indicates if the model exists. */ + public $exists = false; + + /** + * Required but not implemented by HasAttributes in several places that define casting behavior + * @return array + */ + public function getDates(): array + { + return $this->dates; + } + + /** + * required by HasAttributes::getCasts but can not be true for JsonModel + * @return bool + */ + public function getIncrementing(): bool + { + return false; + } + + /* + * ==== All of these are cloned from Model, but seem like they should be in HasAttributes. + */ + /** + * Dynamically retrieve attributes on the model. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this->getAttribute($key); + } + + /** + * Dynamically set attributes on the model. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function __set($key, $value) + { + // NOTE: This isn't general purpose recursion prevention, but it does catch one case, cheaply + if ($value === $this) { + throw new \RuntimeException('Cannot set a recursive property.'); + } + $this->setAttribute($key, $value); + } + + /** + * Determine if an attribute or relation exists on the model. + * + * @param string $key + * @return bool + */ + public function __isset($key) + { + return $this->offsetExists($key); + } + + /** + * Allow for native unset() calls. + * + * @param $key + */ + public function __unset($key) + { + $this->offsetUnset($key); + } + + /** + * Determine if the given attribute exists. + * + * @param mixed $offset + * @return bool + */ + public function offsetExists(mixed $offset): bool + { + return !is_null($this->getAttribute($offset)); + } + + /** + * Get the value for a given offset. + */ + public function offsetGet(mixed $offset): mixed + { + return $this->getAttribute($offset); + } + + /** + * Set the value for a given offset. + * + * @param mixed $offset + * @param mixed $value + * @return void + */ + public function offsetSet(mixed $offset, mixed $value): void + { + $this->setAttribute($offset, $value); + } + + /** + * Unset the value for a given offset. + * + * @param mixed $offset + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->attributes[$offset], $this->relations[$offset]); + } + + // JsonModels don't support preventAccessingMissingAttributes yet + protected function throwMissingAttributeExceptionIfApplicable($key) + { + return null; + } + /** + * ============= End section that "should be in HasAttributes" + */ + + /** + * ============== Cloned from Model but should be in HasEvents + */ + + /** + * The event dispatcher instance. + * + * @var \Illuminate\Contracts\Events\Dispatcher + */ + protected static $dispatcher; + + /** + * The array of booted models. + * + * @var array + */ + protected static $booted = []; + + /** + * Check if the model needs to be booted and if so, do it. + * + * @return void + */ + protected function bootIfNotBooted() + { + if (!isset(static::$booted[static::class])) { + static::$booted[static::class] = true; + + $this->fireModelEvent('booting', false); + + static::boot(); + + $this->fireModelEvent('booted', false); + } + } + + /** + * The "booting" method of the model. + * + * @return void + */ + protected static function boot() + { + static::bootTraits(); + } + + /** + * Boot all of the bootable traits on the model. + * + * @return void + */ + protected static function bootTraits() + { + $class = static::class; + + foreach (class_uses_recursive($class) as $trait) { + if (method_exists($class, $method = 'boot' . class_basename($trait))) { + forward_static_call([$class, $method]); + } + } + } + + /** + * Clear the list of booted models so they will be re-booted. + * + * @return void + */ + public static function clearBootedModels() + { + static::$booted = []; + } + /** + * ============= End section that "should be in HasEvents" + */ + + /** + * JsonModel constructor. + * Takes three constructor signatures: + * If called with an array, fill the private $attributes array. This is serializable, but not saveable. + * If called with a model and string attribute, + * load that attribute (usually a JSON column) to fill this model's attributes. + * "Link" to the model and attribute for saving. + * If called with a model, string attribute, and string key, + * load the attribute then look for that string key in that data (e.g. $deal->data['vehicle']) + * to fill this model's attributes + * "Link" to the model, attribute, and key for saving. + * @param mixed ...$params + */ + public function __construct(...$params) + { + $this->bootIfNotBooted(); + + if (empty($params)) { + $params = [[]]; // Act like one param, empty data + } + if (is_array($params[0])) { + $this->syncOriginal(); + $this->fill($params[0]); + } elseif ((is_a($params[0], Model::class) || is_a($params[0], JsonModel::class)) && is_string($params[1])) { + $this->link(...$params); + $this->fresh(); + } else { + throw new \InvalidArgumentException(static::class . " couldn't understand the construct signature"); + } + + // If we have json model attributes, hydrate them + if ((new ClassUsesTrait())($this, HasJsonModelAttributes::class)) { + $this->hydrateAllJsonModelAttributes(); + } + } + + /** + * If this JsonModel is linked, download data over the link, and + * fill internal attributes + * @return JsonModel self (for chaining) + * @throws \DomainException if the JsonModel isn't linked + */ + public function fresh(): JsonModel + { + if ($this->hasJsonModelAttributes()) { + $this->emptyJsonModelAttributeCache(); + } + + $linkedData = $this->getLinkedData(); + /** + * If we somehow have an object, turn it back into an array. + */ + if (is_object($linkedData)) { + $linkedData = (array) $linkedData->toArray(); + } + $this->fill($linkedData); + // If you are being freshened from nothing, you are new, + // 'exists' flag will cause your 'creating' and 'created' events to fire + $this->exists = $linkedData !== null; + $this->syncOriginal(); + return $this; + } + + /** + * This object has no properties + * @return bool + */ + public function isEmpty(): bool + { + return empty($this->attributes); + } + + /** + * Use the HasAttributes concern to get casting, but basically just fill a private variable with data. + * @param array|null $attributes + * @return JsonModel self (for chaining) + */ + public function fill(?array $attributes): JsonModel + { + if (is_array($attributes)) { + foreach ($attributes as $key => $value) { + $this->setAttribute($key, $value); + } + } + return $this; + } + + /** + * Update attributes, and if configured for it, save. + * @param array $attributes + * @return bool was save successful? + */ + public function update(array $attributes): bool + { + return $this->fill($attributes)->save(); + } + + /** + * Update attributes on a JsonModel with JsonModel children safely without destroying the children's values. + * + * @Note: This might behave unexpectedly if $this->{$key} is currently null. E.g., the data gets set raw on the + * parent, but the child does not get instantiated, so saving handlers and validation don’t get called. + * @param array $attributes + * @param bool $isRootOfChange + * @return void + */ + public function updateRecursive(array $attributes, bool $isRootOfChange = true) + { + foreach ($attributes as $key => $updatedAttribute) { + if ($this->{$key} instanceof JsonModel) { + $this->{$key}->updateRecursive($updatedAttribute, false); + } else { + $this->{$key} = $updatedAttribute; + } + } + if ($isRootOfChange) { + $this->save(); + } + } + + /** + * Given a set of changes that *might* contain some invalid data, + * take the good parts and throw out the rest. + * Assumes that $this was valid before the changes, and that each key could be an independent change, + * so if you have validation states where two attributes have to agree, choose `update` instead. + * @param array $attributes + * @return void + */ + public function safeUpdate(array $attributes): void + { + foreach ($attributes as $key => $updatedAttribute) { + $wasSet = isset($this->{$key}); + $previousValue = $this->{$key}; // __get will fill null even if it wasn't null + $this->{$key} = $updatedAttribute; + try { + $this->validateOrThrow(); + } catch (JsonSchemaValidationException) { + if ($wasSet) { + $this->{$key} = $previousValue; + } else { + unset($this->{$key}); + } + } + } + $this->save(); + } + + /** + * Save the data over the link: + * + * Fire the saving event. If a saving event handler returns false, return false without saving + * Put data on the upstream model using set_linked_data (this centralizes checks for isLinked and the Schema) + * Save the upstream model. If the model rejects the save, return false. + * Fire the saved event. + * @return bool Was saving successful? + */ + public function save(): bool + { + if ($this->preSave() === false) { + return false; + } + + $this->setLinkedData(); + $saved = $this->upstream_model->save(); + + if ($saved) { + $this->postSave(); + } + + return $saved; + } + + /** + * Delete local $attributes and set a NULL on the upstream model. + * + * Fire the deleting event. If a deleting event handler returns false, return false without deleting + * reset local $attributes to empty array and set a NULL on the upstream model + * (using unset_linked_data centralizes checks for isLinked and the Schema) + * Save the upstream model. If the model rejects the save, return false. + * Fire the deleted event. + * + * @return bool + */ + public function delete(): bool + { + if ($this->fireModelEvent('deleting') === false) { + return false; + } + + $this->attributes = []; + $this->setLinkedData(); + $deleted = $this->upstream_model->save(); + + if ($deleted) { + $this->fireModelEvent('deleted'); + $this->exists = false; + } + + return $deleted; + } + + /** + * Use attribute casting to turn this object's $attributes back into an array. + * Note, contrary to it's name this is really just a "prepare for JSON" + * method and the return type is not strictly Array + * @return array|null|object + */ + public function toArray() + { + // In JSON terms, no one expects a JsonModel to be an empty array `[]` + // Instead, return an empty object that serializes to `{}` + if ($this->isEmpty()) { + return (object) []; + } + + return $this->attributesToArray(); + } + + /** + * Implement PHP's JsonSerializable contract + */ + public function jsonSerialize(): object|array|null + { + return $this->toArray(); + } + + /** + * Cast this object back to a JSON string + * @param int $options see http://php.net/manual/en/function.json-encode.php + * @return string + * @throws JsonEncodingException + */ + public function toJson($options = 0): string + { + $asArray = $this->toArray(); + return [] === $asArray ? '{}' : json_encode($asArray, $options | JSON_THROW_ON_ERROR); + } + + /** + * Runs any pre-save events. This is basically a visibility hack, + * so we can run the protected model events (implemented by HasEvents) + * from outside the model (esp from CollectionOfJsonModels and HasJsonModelAttributes) + * + * If the listener returned literal false, this returns false, + * and the caller should halt the save. + * @return bool + */ + public function preSave(): bool + { + if (!$this->exists && $this->fireModelEvent('creating') === false) { + return false; + } + if ($this->fireModelEvent('saving') === false) { + return false; + } + return $this->cascadePreSave(); + } + + /** + * Runs any post-save events. This is a visibility hack, see above. + * These can't affect behavior, so there is no return value + */ + public function postSave(): void + { + $this->fireModelEvent('saved'); + if (!$this->exists) { + $this->fireModelEvent('created'); + $this->exists = true; + } + $this->cascadePostSave(); + $this->syncOriginal(); + } + + /** + * This is used by Laravel Models to optionally enable a custom collection + * e.g. when you call a factory to return multiples. + * Can be overridden by children (e.g. Vehicle returns CollectionOfVehicles) + * @param array $models + * @return Collection + */ + public function newCollection(array $models = []): Collection + { + return (new CollectionOfJsonModels($models))->setType(static::class); + } + + /** + * Returns this object flattened entirely down to PHP language primitives, (usually an associative array) + * by JSON-encoding and then JSON-decoding + */ + public function mugglify() + { + return Json::mugglify($this); + } + + /** + * By default, a JsonModel doesn't have children to cascade to, so this is a no-op + * But in conjunction with the HasJsonModelAttributes, this gets overwritten with a useful implementation. + * @return bool + */ + public function cascadePreSave(): bool + { + return true; + } + + /** + * By default, a JsonModel doesn't have children to cascade to, so this is a no-op + * But in conjunction with the HasJsonModelAttributes, this gets overwritten with a useful implementation. + */ + public function cascadePostSave(): void + { + return; + } + + /** + * This method controls the behavior of HasJsonModelAttributes, when you try to __get a Json Model Attribute + * By default, a JsonModel is never null, this is only useful when overridden by NullWhenUsedAsAttributeWhenEmpty + * @return bool + */ + public function nullWhenUsedAsAttribute(): bool + { + return false; + } + + /** + * When casting Carbon attributes for storage, use ISO-8601 + * This overrides a method in HasAttributes that otherwise requires knowledge of the database connection type, + * which obviously doesn't apply + * @return string + */ + public function getDateFormat(): string + { + return 'c'; + } + + public function hasJsonModelAttributes(): bool + { + return (new ClassUsesTrait())(class: $this, trait: HasJsonModelAttributes::class); + } +} diff --git a/app/Traits/HasJsonModelAttributes.php b/app/Traits/HasJsonModelAttributes.php new file mode 100644 index 0000000..f0b04b4 --- /dev/null +++ b/app/Traits/HasJsonModelAttributes.php @@ -0,0 +1,362 @@ + ['ClassName', 'attribute'] + * When you get $this->{$key} we actually return new ClassName($this->{'attribute'}) + * + * or + * + * 'key' => ['ClassName', 'attribute', 'attribute-key'] + * When you get $this->{$key} we actually return new ClassName($this->{'attribute'}['attribute-key']) + * + * Note that the way other code (e.g. JsonModel::toArray) looks for this trait, + * this trait must be added to the concrete child, it can't be inherited. + */ + +declare(strict_types=1); + +namespace Carsdotcom\LaravelJsonModel\Traits; + +use Carsdotcom\LaravelJsonModel\CollectionOfJsonModels; +use Carsdotcom\LaravelJsonModel\Helpers\Json; +use Carsdotcom\LaravelJsonModel\Contracts\CanCascadeEvents; +use Carsdotcom\LaravelJsonModel\JsonModel; +use DomainException; +use Illuminate\Support\Arr; +use InvalidArgumentException; +use stdClass; + +trait HasJsonModelAttributes +{ + /** + * @var array + * In-memory cache so if you come back to an attribute by key + * You get the same object. Lets you make incremental changes. + */ + protected $jsonModelAttributeCache = []; + + /** + * When __get()ing a recognized attribute in the jsonModelAttributes array, try to hydrate the expected class. + * If there's no data, return a clean null. + * If the $key is not in the jsonModelAttributes array this Trait is responsible for, + * kick the ball up to the parent. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + $config = $this->isJsonModelAttribute($key); + if (!$config) { + return parent::__get($key); + } + if (array_key_exists($key, $this->jsonModelAttributeCache)) { + return $this->jsonModelAttributeCache[$key]; + } + list($type, $attribute, $attribute_key, $is_collection, $primary_key) = $config; + if ($is_collection) { + /** @var CollectionOfJsonModels $collection */ + $collection = (new $type())->newCollection(); + return $this->jsonModelAttributeCache[$key] = $collection + ->setPrimaryKey($primary_key) + ->link($this, $attribute, $attribute_key) + ->fresh(); + } + + /** @var JsonModel $linked */ + $linked = new $type($this, $attribute, $attribute_key); + if ($linked->nullWhenUsedAsAttribute()) { + return null; + } + $this->jsonModelAttributeCache[$key] = $linked; + return $linked; + } + + /** + * Determine if a Json Model Attribute exists, otherwise defer to parent + * + * @param string $key + * @return bool + */ + public function __isset($key) + { + if ($this->isJsonModelAttribute($key)) { + return $this->{$key} ? !$this->{$key}->isEmpty() : false; + } + return parent::__isset($key); + } + + /** + * When __set()ing a recognized attribute in the jsonModelAttributes array: + * If the value is already the correct type, use its save method (this gets us casting, observers, etc) + * If the value is an array, try to turn it into the correct class and then save (same reason) + * If the $key is not in the jsonModelAttributes array this Trait is responsible for, + * kick the ball up to the parent. + * @param string $key + * @param mixed $value + * @return void + * @throws InvalidArgumentException + */ + public function __set($key, $value) + { + $config = $this->isJsonModelAttribute($key); + if (!$config) { + parent::__set($key, $value); + return; + } + + list($type, $attribute, $attribute_key, $is_collection, $primary_key) = $config; + if (!$is_collection) { + //Setting it to raw array data, build up an object + if (is_array($value)) { + /** @var JsonModel $value */ + $value = new $type($value); + } + + if (!($value instanceof $type)) { + throw new InvalidArgumentException("{$key} must be a {$type} or valid array"); + } + + /** @var JsonModel $value */ + $value->link($this, $attribute, $attribute_key); + $value->preSave(); + $value->setLinkedData(); + + $this->jsonModelAttributeCache[$key] = $value; + } else { + /** @var CollectionOfJsonModels $collection */ + $collection = (new $type())->newCollection() + ->setPrimaryKey($primary_key) + ->link($this, $attribute, $attribute_key); + if (is_iterable($value)) { + foreach ($value as $each) { + // This has the side effect of casting children + $collection->push($each); + } + } else { + throw new InvalidArgumentException("{$key} must be iterable"); + } + $collection->setLinkedData(); + $this->jsonModelAttributeCache[$key] = $collection; + } + + return; + } + + /** + * Parent has been explicitly asked to remove a Json Model Attribute + * Type declaration must be compatible with Laravel Model + * @param string $key + * @return void + */ + public function __unset($key) + { + $config = $this->isJsonModelAttribute($key); + if (!$config) { + parent::__unset($key); + return; + } + Arr::forget($this->jsonModelAttributeCache, $key); + list($type, $attribute, $attribute_key, $is_collection, $primary_key) = $config; + if ($attribute_key) { + $wholeAttribute = $this->{$attribute}; + unset($wholeAttribute[$attribute_key]); + $this->{$attribute} = $wholeAttribute; + } else { + Arr::forget($this->attributes, $attribute); + } + } + + /** + * Override to normal Laravel toArray functionality. + * We ask all our hydrated Json Model Attributes in cache to + * serialize themselves because our local representation of them can be less fresh + * @return array|null|stdClass + */ + public function toArray() + { + $array = parent::toArray(); + if ($this->jsonModelAttributeCache) { + // Our conception of ourself could be null or even stdClass. + // convert to array only if absolutely necessary + if (!is_array($array)) { + $array = (array)$array; + } + foreach ($this->jsonModelAttributeCache as $key => $value) { + // None of our models are safe to encode to empty object, because they tend to get flattened back to [] + if (json_encode($value) === '{}') { + Arr::forget($array, $key); + continue; + } + $array[$key] = $value; + } + + // After removing empty properties, I have no properties, I am empty + if (!$array) { + return (object)[]; + } + } + + return $array; + } + + /** + * Functions like eager loading a Laravel Relationship. + * Forget all currently cached Json Model Attributes, + * then hydrate all the models requested. + * This is a good way to make sure the JSON you're about to emit + * contains all the relationships your caller needs. + * @param array $keys + * @return HasJsonModelAttributes + */ + public function withJsonModelAttributes(array $keys): self + { + $this->emptyJsonModelAttributeCache(); + foreach ($keys as $key) { + $this->__get($key); // Prime the cache with the getter + } + return $this; + } + + protected function jsonModelAttributesConfigIsValid() + { + if (!property_exists($this, 'jsonModelAttributes')) { + throw new DomainException( + static::class . " must define a property jsonModelAttributes to use HasJsonModelAttributes" + ); + } + if (!is_array($this->jsonModelAttributes)) { + throw new DomainException(static::class . " must define an array for property jsonModelAttributes"); + } + } + + /** + * Given a key, if this attribute is responsible for it, return the config tuple. + * If we're not responsible for it, return null. + * @param $key + * @return array|null + * @throws DomainException if our config in jsonModelAttributes is malformed. + */ + protected function isJsonModelAttribute($key): ?array + { + $this->jsonModelAttributesConfigIsValid(); + if (array_key_exists($key, $this->jsonModelAttributes)) { + $tuple = $this->jsonModelAttributes[$key]; + if (!is_array($tuple) || count($tuple) < 2 || count($tuple) > 5) { + throw new DomainException("Unusable jsonModelAttributes in " . static::class . " for $key"); + } + // Create a consistent four-item config but let definer be lazy + if (!isset($tuple[2])) { + $tuple[2] = null; + } + if (!isset($tuple[3])) { + $tuple[3] = CollectionOfJsonModels::NOT_A; + } + if (!isset($tuple[4])) { + $tuple[4] = null; + } + return $tuple; + } + return null; + } + + /** + * Empty the JsonModel attribute cache. + * This is especially useful when the underlying storage mechanism changes + */ + public function emptyJsonModelAttributeCache(): void + { + $this->jsonModelAttributeCache = []; + } + + /** + * Does this attribute have changes that haven't been written? + * @param $key + * @return bool + */ + public function isJsonModelAttributeDirty($key): bool + { + $config = $this->isJsonModelAttribute($key); + if (!$config) { + throw new InvalidArgumentException("{$key} must be a Json Model Attribute"); + } + if (!isset($this->jsonModelAttributeCache[$key])) { + // Not even hydrated, can't possibly be dirty + return false; + } + + $accordingToChild = json_encode($this->jsonModelAttributeCache[$key]); + + list($type, $attribute, $attribute_key, $is_collection, $primary_key) = $config; + + if ($attribute_key) { + $accordingToParent = json_encode($this->original[$attribute][$attribute_key] ?? null); + } else { + $accordingToParent = json_encode($this->original[$attribute] ?? null); + } + return Json::canonicalize($accordingToChild) !== Json::canonicalize($accordingToParent); + } + + /** + * This is called in the constructor for JsonModels + * When waking up a JsonModel from raw JSON, hydrate all the children immediately + * so future serialization events can use things like magic attributes. + */ + public function hydrateAllJsonModelAttributes(): void + { + $this->jsonModelAttributesConfigIsValid(); + foreach ($this->jsonModelAttributes as $key => $ignore) { + $this->__get($key); + } + } + + /** + * Iterate over every hydrated attribute in the cache and tell each + * to call its own preSave, or cascadePreSave to its own children + * @return bool + */ + public function cascadePreSave(): bool + { + foreach ($this->jsonModelAttributeCache as $child) { + if ($child instanceof CanCascadeEvents) { + if ($child->preSave() === false) { + return false; + } + } + } + return true; + } + + /** + * Iterate over every hydrated attribute in the cache and tell each + * to call its own postSave, or cascadePostSave to its own children + */ + public function cascadePostSave(): void + { + foreach ($this->jsonModelAttributeCache as $child) { + if ($child instanceof CanCascadeEvents) { + $child->postSave(); + } + } + } + + /** + * I am empty if ALL my descendents are empty, and I am locally empty + * @return bool + */ + public function isEmpty(): bool + { + return collect($this->jsonModelAttributeCache)->every->isEmpty() + && parent::isEmpty(); + } +} diff --git a/app/Traits/HasLinkedData.php b/app/Traits/HasLinkedData.php new file mode 100644 index 0000000..ca522d8 --- /dev/null +++ b/app/Traits/HasLinkedData.php @@ -0,0 +1,127 @@ +upstream_model = $model; + $this->upstream_attribute = $attribute; + $this->upstream_key = $key; + return $this; + } + + /** + * Is this model instance linked to an upstream model that can be used to save and load? + * @return bool + */ + public function isLinked(): bool + { + return $this->upstream_model && $this->upstream_attribute; + } + + /** + * Get data over the link to the model + * In contrast to ->fresh, this method does NOT update local attributes. + * @return mixed + * Note, return type is *typically* an associative array + * @throws DomainException if the JsonModel isn't linked + */ + public function getLinkedData() + { + if (!$this->isLinked()) { + throw new DomainException("JsonModel isn't linked"); + } + + $linked_data = $this->upstream_model[$this->upstream_attribute]; + if ($this->upstream_key) { + $linked_data = Arr::get($linked_data, $this->upstream_key, null); + } + return $linked_data; + } + + /** + * Apply changes to the upstream model, but don't persist them to the database. + * This is equivalent to setting an attribute, e.g. $model->thing = 5 doesn't persist immediately + * @return void + * @throws DomainException|Exception if the JsonModel isn't linked + */ + public function setLinkedData(): void + { + if (!$this->isLinked()) { + throw new DomainException("JsonModel isn't linked"); + } + + if (in_array(HasJsonModelAttributes::class, class_uses_recursive($this))) { + foreach ($this->jsonModelAttributeCache as $child) { + $child->setLinkedData(); + } + } + + if ($this instanceof CanValidate) { + $this->validateOrThrow(); + } + + $new_value = $this->toArray(); + if ($this->upstream_key) { + $whole_attribute = $this->upstream_model[$this->upstream_attribute]; + // The return value of array_set() is not "old with changes" it's way, way dumber than that + Arr::set($whole_attribute, $this->upstream_key, $new_value); + $new_value = $whole_attribute; + } + + $this->upstream_model[$this->upstream_attribute] = $new_value; + } + + /** + * @param string $class + * @return JsonModel|Model|null + */ + public function getAncestorOfType(string $class) + { + if (!$this->isLinked()) { + return null; + } + if (is_a($this->upstream_model, $class)) { + return $this->upstream_model; + } + if (!method_exists($this->upstream_model, 'getAncestorOfType')) { + return null; + } + return $this->upstream_model->getAncestorOfType($class); + } + + /** + * @param string $class + * @return bool + */ + public function isLinkedToInstanceOf(string $class): bool + { + return $this->isLinked() + && $this->upstream_model instanceof $class; + } +} diff --git a/app/Traits/NullWhenUsedAsAttributeWhenEmpty.php b/app/Traits/NullWhenUsedAsAttributeWhenEmpty.php new file mode 100644 index 0000000..7bd6fde --- /dev/null +++ b/app/Traits/NullWhenUsedAsAttributeWhenEmpty.php @@ -0,0 +1,41 @@ +reservation) + * if this model is empty or missing, it will return NULL when the getter runs. + * + * This makes it possible to write fluent conditionals because this object is routinely expected to be absent. + * e.g.: + * if ($deal->cosigner) + * if ($vehicle->reservation) + * if ($vehicle->payments->dealer) + * + * The assumption is that these models will be created and saved atomically, not built up from individual props. + * This will work: + * $deal->cosigner = new Person(); + * $vehicle->reservation = ['message' => 'gimme']; + * + * This will NOT work if the dealer offer hasn't been started, these would throw "cannot set property 'type' of NULL" + * $vehicle->payments->dealer->type = 'loan'; + * $vehicle->payments->dealer->down_payment = 123; + */ +declare(strict_types=1); + +namespace Carsdotcom\LaravelJsonModel\Traits; + +/** + * Trait nullWhenUsedAsAttributeWhenEmpty + * @package Carsdotcom\LaravelJsonModel\Traits + */ +trait NullWhenUsedAsAttributeWhenEmpty +{ + /** + * This method controls the behavior of HasJsonModelAttributes, when you try to __get a Json Model Attribute + * If the constructed version of this model is empty, the attribute accessor on the parent will return NULL + * @return bool + */ + public function nullWhenUsedAsAttribute(): bool + { + return $this->isEmpty(); + } + +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..257c983 --- /dev/null +++ b/composer.json @@ -0,0 +1,46 @@ +{ + "name": "carsdotcom/laravel-json-model", + "type": "library", + "description": "Laravel models backed by JSON data", + "keywords": ["library", "laravel"], + "license": "UNLICENSED", + "require": { + "carsdotcom/laravel-json-schema": "^v1.0.1", + "ext-json": "*", + "php": "^8.1", + "laravel/framework": "^9.19" + }, + "require-dev": { + "brianium/paratest": "^6.6", + "fakerphp/faker": "^1.9.1", + "mockery/mockery": "^1.4.4", + "nunomaduro/collision": "^6.1", + "orchestra/testbench": "^7.11", + "phpstan/phpstan": "^1.8", + "phpunit/phpunit": "^9.5.10", + "squizlabs/php_codesniffer": "^3.7" + }, + "autoload": { + "psr-4": { + "Carsdotcom\\LaravelJsonModel\\": "app/" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests/", + "Database\\": "database/" + } + }, + "config": { + "optimize-autoloader": true, + "preferred-install": "dist", + "sort-packages": true + }, + "minimum-stability": "dev", + "prefer-stable": true, + "scripts": { + "post-autoload-dump": [ + "@php ./vendor/bin/testbench package:discover --ansi" + ] + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..3c99d07 --- /dev/null +++ b/composer.lock @@ -0,0 +1,8558 @@ +{ + "_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": "08d555c9c0eefdc7cdb21293bfc38f27", + "packages": [ + { + "name": "brick/math", + "version": "0.11.0", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "0ad82ce168c82ba30d1c01ec86116ab52f589478" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/0ad82ce168c82ba30d1c01ec86116ab52f589478", + "reference": "0ad82ce168c82ba30d1c01ec86116ab52f589478", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^9.0", + "vimeo/psalm": "5.0.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", + "brick", + "math" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.11.0" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + } + ], + "time": "2023-01-15T23:15:59+00:00" + }, + { + "name": "carsdotcom/laravel-json-schema", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/carsdotcom/laravel-json-schema.git", + "reference": "5590c317aabe87e9f0ea7ed02e921b242e210102" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/carsdotcom/laravel-json-schema/zipball/5590c317aabe87e9f0ea7ed02e921b242e210102", + "reference": "5590c317aabe87e9f0ea7ed02e921b242e210102", + "shasum": "" + }, + "require": { + "laravel/framework": "^9.19", + "opis/json-schema": "^2.3", + "php": "^8.1" + }, + "require-dev": { + "brianium/paratest": "^6.6", + "fakerphp/faker": "^1.9.1", + "mockery/mockery": "^1.4.4", + "nunomaduro/collision": "^6.1", + "orchestra/testbench": "^7.11", + "phpstan/phpstan": "^1.8", + "phpunit/phpunit": "^9.5.10", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Carsdotcom\\JsonSchemaValidation\\SchemaValidatorProvider" + ], + "aliases": { + "SchemaValidator": "Carsdotcom\\JsonSchemaValidation\\SchemaValidator" + } + } + }, + "autoload": { + "psr-4": { + "Carsdotcom\\JsonSchemaValidation\\": "app/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Json Schema validation for Laravel projects", + "keywords": [ + "JsonSchema", + "laravel", + "library", + "validation" + ], + "support": { + "issues": "https://github.com/carsdotcom/laravel-json-schema/issues", + "source": "https://github.com/carsdotcom/laravel-json-schema/tree/v1.0.1" + }, + "time": "2023-07-10T20:33:55+00:00" + }, + { + "name": "dflydev/dot-access-data", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "f41715465d65213d644d3141a6a93081be5d3549" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/f41715465d65213d644d3141a6a93081be5d3549", + "reference": "f41715465d65213d644d3141a6a93081be5d3549", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.2" + }, + "time": "2022-10-27T11:44:00+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.0.8", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "f9301a5b2fb1216b2b08f02ba04dc45423db6bff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/f9301a5b2fb1216b2b08f02ba04dc45423db6bff", + "reference": "f9301a5b2fb1216b2b08f02ba04dc45423db6bff", + "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.0.8" + }, + "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": "2023-06-16T13:40:37+00:00" + }, + { + "name": "doctrine/lexer", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "84a527db05647743d50373e0ec53a152f2cde568" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/84a527db05647743d50373e0ec53a152f2cde568", + "reference": "84a527db05647743d50373e0ec53a152f2cde568", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^10", + "phpstan/phpstan": "^1.9", + "phpunit/phpunit": "^9.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^5.0" + }, + "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.0.0" + }, + "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": "2022-12-15T16:57:16+00:00" + }, + { + "name": "dragonmantank/cron-expression", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/dragonmantank/cron-expression.git", + "reference": "782ca5968ab8b954773518e9e49a6f892a34b2a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/782ca5968ab8b954773518e9e49a6f892a34b2a8", + "reference": "782ca5968ab8b954773518e9e49a6f892a34b2a8", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "webmozart/assert": "^1.0" + }, + "replace": { + "mtdowling/cron-expression": "^1.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-webmozart-assert": "^1.0", + "phpunit/phpunit": "^7.0|^8.0|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Cron\\": "src/Cron/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Tankersley", + "email": "chris@ctankersley.com", + "homepage": "https://github.com/dragonmantank" + } + ], + "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due", + "keywords": [ + "cron", + "schedule" + ], + "support": { + "issues": "https://github.com/dragonmantank/cron-expression/issues", + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.2" + }, + "funding": [ + { + "url": "https://github.com/dragonmantank", + "type": "github" + } + ], + "time": "2022-09-10T18:51:20+00:00" + }, + { + "name": "egulias/email-validator", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "3a85486b709bc384dae8eb78fb2eec649bdb64ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/3a85486b709bc384dae8eb78fb2eec649bdb64ff", + "reference": "3a85486b709bc384dae8eb78fb2eec649bdb64ff", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^2.0 || ^3.0", + "php": ">=8.1", + "symfony/polyfill-intl-idn": "^1.26" + }, + "require-dev": { + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^4.30" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" + }, + "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.0.1" + }, + "funding": [ + { + "url": "https://github.com/egulias", + "type": "github" + } + ], + "time": "2023-01-14T14:17:03+00:00" + }, + { + "name": "fruitcake/php-cors", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/fruitcake/php-cors.git", + "reference": "58571acbaa5f9f462c9c77e911700ac66f446d4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/58571acbaa5f9f462c9c77e911700ac66f446d4e", + "reference": "58571acbaa5f9f462c9c77e911700ac66f446d4e", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0", + "symfony/http-foundation": "^4.4|^5.4|^6" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "phpunit/phpunit": "^9", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Fruitcake\\Cors\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fruitcake", + "homepage": "https://fruitcake.nl" + }, + { + "name": "Barryvdh", + "email": "barryvdh@gmail.com" + } + ], + "description": "Cross-origin resource sharing library for the Symfony HttpFoundation", + "homepage": "https://github.com/fruitcake/php-cors", + "keywords": [ + "cors", + "laravel", + "symfony" + ], + "support": { + "issues": "https://github.com/fruitcake/php-cors/issues", + "source": "https://github.com/fruitcake/php-cors/tree/v1.2.0" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2022-02-20T15:07:15+00:00" + }, + { + "name": "graham-campbell/result-type", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831", + "reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2023-02-25T20:23:15+00:00" + }, + { + "name": "guzzlehttp/uri-template", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/uri-template.git", + "reference": "b945d74a55a25a949158444f09ec0d3c120d69e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/b945d74a55a25a949158444f09ec0d3c120d69e2", + "reference": "b945d74a55a25a949158444f09ec0d3c120d69e2", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "symfony/polyfill-php80": "^1.17" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.19 || ^9.5.8", + "uri-template/tests": "1.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\UriTemplate\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + } + ], + "description": "A polyfill class for uri_template of PHP", + "keywords": [ + "guzzlehttp", + "uri-template" + ], + "support": { + "issues": "https://github.com/guzzle/uri-template/issues", + "source": "https://github.com/guzzle/uri-template/tree/v1.0.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/uri-template", + "type": "tidelift" + } + ], + "time": "2021-10-07T12:57:01+00:00" + }, + { + "name": "laravel/framework", + "version": "v9.52.10", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "858add225ce88a76c43aec0e7866288321ee0ee9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/858add225ce88a76c43aec0e7866288321ee0ee9", + "reference": "858add225ce88a76c43aec0e7866288321ee0ee9", + "shasum": "" + }, + "require": { + "brick/math": "^0.9.3|^0.10.2|^0.11", + "doctrine/inflector": "^2.0.5", + "dragonmantank/cron-expression": "^3.3.2", + "egulias/email-validator": "^3.2.1|^4.0", + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "ext-session": "*", + "ext-tokenizer": "*", + "fruitcake/php-cors": "^1.2", + "guzzlehttp/uri-template": "^1.0", + "laravel/serializable-closure": "^1.2.2", + "league/commonmark": "^2.2.1", + "league/flysystem": "^3.8.0", + "monolog/monolog": "^2.0", + "nesbot/carbon": "^2.62.1", + "nunomaduro/termwind": "^1.13", + "php": "^8.0.2", + "psr/container": "^1.1.1|^2.0.1", + "psr/log": "^1.0|^2.0|^3.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "ramsey/uuid": "^4.7", + "symfony/console": "^6.0.9", + "symfony/error-handler": "^6.0", + "symfony/finder": "^6.0", + "symfony/http-foundation": "^6.0", + "symfony/http-kernel": "^6.0", + "symfony/mailer": "^6.0", + "symfony/mime": "^6.0", + "symfony/process": "^6.0", + "symfony/routing": "^6.0", + "symfony/uid": "^6.0", + "symfony/var-dumper": "^6.0", + "tijsverkoyen/css-to-inline-styles": "^2.2.5", + "vlucas/phpdotenv": "^5.4.1", + "voku/portable-ascii": "^2.0" + }, + "conflict": { + "tightenco/collect": "<5.5.33" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0" + }, + "replace": { + "illuminate/auth": "self.version", + "illuminate/broadcasting": "self.version", + "illuminate/bus": "self.version", + "illuminate/cache": "self.version", + "illuminate/collections": "self.version", + "illuminate/conditionable": "self.version", + "illuminate/config": "self.version", + "illuminate/console": "self.version", + "illuminate/container": "self.version", + "illuminate/contracts": "self.version", + "illuminate/cookie": "self.version", + "illuminate/database": "self.version", + "illuminate/encryption": "self.version", + "illuminate/events": "self.version", + "illuminate/filesystem": "self.version", + "illuminate/hashing": "self.version", + "illuminate/http": "self.version", + "illuminate/log": "self.version", + "illuminate/macroable": "self.version", + "illuminate/mail": "self.version", + "illuminate/notifications": "self.version", + "illuminate/pagination": "self.version", + "illuminate/pipeline": "self.version", + "illuminate/queue": "self.version", + "illuminate/redis": "self.version", + "illuminate/routing": "self.version", + "illuminate/session": "self.version", + "illuminate/support": "self.version", + "illuminate/testing": "self.version", + "illuminate/translation": "self.version", + "illuminate/validation": "self.version", + "illuminate/view": "self.version" + }, + "require-dev": { + "ably/ably-php": "^1.0", + "aws/aws-sdk-php": "^3.235.5", + "doctrine/dbal": "^2.13.3|^3.1.4", + "ext-gmp": "*", + "fakerphp/faker": "^1.21", + "guzzlehttp/guzzle": "^7.5", + "league/flysystem-aws-s3-v3": "^3.0", + "league/flysystem-ftp": "^3.0", + "league/flysystem-path-prefixing": "^3.3", + "league/flysystem-read-only": "^3.3", + "league/flysystem-sftp-v3": "^3.0", + "mockery/mockery": "^1.5.1", + "orchestra/testbench-core": "^7.24", + "pda/pheanstalk": "^4.0", + "phpstan/phpdoc-parser": "^1.15", + "phpstan/phpstan": "^1.4.7", + "phpunit/phpunit": "^9.5.8", + "predis/predis": "^1.1.9|^2.0.2", + "symfony/cache": "^6.0", + "symfony/http-client": "^6.0" + }, + "suggest": { + "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", + "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.235.5).", + "brianium/paratest": "Required to run tests in parallel (^6.0).", + "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.13.3|^3.1.4).", + "ext-apcu": "Required to use the APC cache driver.", + "ext-fileinfo": "Required to use the Filesystem class.", + "ext-ftp": "Required to use the Flysystem FTP driver.", + "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().", + "ext-memcached": "Required to use the memcache cache driver.", + "ext-pcntl": "Required to use all features of the queue worker and console signal trapping.", + "ext-pdo": "Required to use all database features.", + "ext-posix": "Required to use all features of the queue worker.", + "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).", + "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", + "filp/whoops": "Required for friendly error pages in development (^2.14.3).", + "guzzlehttp/guzzle": "Required to use the HTTP Client and the ping methods on schedules (^7.5).", + "laravel/tinker": "Required to use the tinker console command (^2.0).", + "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-path-prefixing": "Required to use the scoped driver (^3.3).", + "league/flysystem-read-only": "Required to use read-only disks (^3.3)", + "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.0).", + "mockery/mockery": "Required to use mocking (^1.5.1).", + "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).", + "pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).", + "phpunit/phpunit": "Required to use assertions and run tests (^9.5.8).", + "predis/predis": "Required to use the predis connector (^1.1.9|^2.0.2).", + "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).", + "symfony/cache": "Required to PSR-6 cache bridge (^6.0).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^6.0).", + "symfony/http-client": "Required to enable support for the Symfony API mail transports (^6.0).", + "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^6.0).", + "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^6.0).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.x-dev" + } + }, + "autoload": { + "files": [ + "src/Illuminate/Collections/helpers.php", + "src/Illuminate/Events/functions.php", + "src/Illuminate/Foundation/helpers.php", + "src/Illuminate/Support/helpers.php" + ], + "psr-4": { + "Illuminate\\": "src/Illuminate/", + "Illuminate\\Support\\": [ + "src/Illuminate/Macroable/", + "src/Illuminate/Collections/", + "src/Illuminate/Conditionable/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Laravel Framework.", + "homepage": "https://laravel.com", + "keywords": [ + "framework", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2023-06-27T13:25:54+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "f23fe9d4e95255dacee1bf3525e0810d1a1b0f37" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/f23fe9d4e95255dacee1bf3525e0810d1a1b0f37", + "reference": "f23fe9d4e95255dacee1bf3525e0810d1a1b0f37", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "nesbot/carbon": "^2.61", + "pestphp/pest": "^1.21.3", + "phpstan/phpstan": "^1.8.2", + "symfony/var-dumper": "^5.4.11" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2023-01-30T18:31:20+00:00" + }, + { + "name": "league/commonmark", + "version": "2.4.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "d44a24690f16b8c1808bf13b1bd54ae4c63ea048" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d44a24690f16b8c1808bf13b1bd54ae4c63ea048", + "reference": "d44a24690f16b8c1808bf13b1bd54ae4c63ea048", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.30.0", + "commonmark/commonmark.js": "0.30.0", + "composer/package-versions-deprecated": "^1.8", + "embed/embed": "^4.4", + "erusev/parsedown": "^1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "^1.4 || ^2.0", + "nyholm/psr7": "^1.5", + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.21", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3 | ^6.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", + "unleashedtech/php-coding-standard": "^3.1.1", + "vimeo/psalm": "^4.24.0 || ^5.0.0" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "support": { + "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", + "issues": "https://github.com/thephpleague/commonmark/issues", + "rss": "https://github.com/thephpleague/commonmark/releases.atom", + "source": "https://github.com/thephpleague/commonmark" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2023-03-24T15:16:10+00:00" + }, + { + "name": "league/config", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2022-12-11T20:36:23+00:00" + }, + { + "name": "league/flysystem", + "version": "3.15.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "a141d430414fcb8bf797a18716b09f759a385bed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a141d430414fcb8bf797a18716b09f759a385bed", + "reference": "a141d430414fcb8bf797a18716b09f759a385bed", + "shasum": "" + }, + "require": { + "league/flysystem-local": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "aws/aws-sdk-php": "3.209.31 || 3.210.0", + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1", + "phpseclib/phpseclib": "3.0.15", + "symfony/http-client": "<5.2" + }, + "require-dev": { + "async-aws/s3": "^1.5", + "async-aws/simple-s3": "^1.1", + "aws/aws-sdk-php": "^3.220.0", + "composer/semver": "^3.0", + "ext-fileinfo": "*", + "ext-ftp": "*", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.5", + "google/cloud-storage": "^1.23", + "microsoft/azure-storage-blob": "^1.1", + "phpseclib/phpseclib": "^3.0.14", + "phpstan/phpstan": "^0.12.26", + "phpunit/phpunit": "^9.5.11", + "sabre/dav": "^4.3.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "File storage abstraction for PHP", + "keywords": [ + "WebDAV", + "aws", + "cloud", + "file", + "files", + "filesystem", + "filesystems", + "ftp", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/3.15.1" + }, + "funding": [ + { + "url": "https://ecologi.com/frankdejonge", + "type": "custom" + }, + { + "url": "https://github.com/frankdejonge", + "type": "github" + } + ], + "time": "2023-05-04T09:04:26+00:00" + }, + { + "name": "league/flysystem-local", + "version": "3.15.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-local.git", + "reference": "543f64c397fefdf9cfeac443ffb6beff602796b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/543f64c397fefdf9cfeac443ffb6beff602796b3", + "reference": "543f64c397fefdf9cfeac443ffb6beff602796b3", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "league/flysystem": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\Local\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Local filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "file", + "files", + "filesystem", + "local" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem-local/issues", + "source": "https://github.com/thephpleague/flysystem-local/tree/3.15.0" + }, + "funding": [ + { + "url": "https://ecologi.com/frankdejonge", + "type": "custom" + }, + { + "url": "https://github.com/frankdejonge", + "type": "github" + } + ], + "time": "2023-05-02T20:02:14+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ff6248ea87a9f116e78edd6002e39e5128a0d4dd", + "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.11.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2022-04-17T13:12:02+00:00" + }, + { + "name": "monolog/monolog", + "version": "2.9.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "f259e2b15fb95494c83f52d3caad003bbf5ffaa1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f259e2b15fb95494c83f52d3caad003bbf5ffaa1", + "reference": "f259e2b15fb95494c83f52d3caad003bbf5ffaa1", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2@dev", + "guzzlehttp/guzzle": "^7.4", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "phpspec/prophecy": "^1.15", + "phpstan/phpstan": "^0.12.91", + "phpunit/phpunit": "^8.5.14", + "predis/predis": "^1.1 || ^2.0", + "rollbar/rollbar": "^1.3 || ^2 || ^3", + "ruflin/elastica": "^7", + "swiftmailer/swiftmailer": "^5.3|^6.0", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/2.9.1" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2023-02-06T13:44:46+00:00" + }, + { + "name": "nesbot/carbon", + "version": "2.68.1", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "4f991ed2a403c85efbc4f23eb4030063fdbe01da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/4f991ed2a403c85efbc4f23eb4030063fdbe01da", + "reference": "4f991ed2a403c85efbc4f23eb4030063fdbe01da", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.1.8 || ^8.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0" + }, + "require-dev": { + "doctrine/dbal": "^2.0 || ^3.1.4", + "doctrine/orm": "^2.7", + "friendsofphp/php-cs-fixer": "^3.0", + "kylekatarnls/multi-tester": "^2.0", + "ondrejmirtes/better-reflection": "*", + "phpmd/phpmd": "^2.9", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.99 || ^1.7.14", + "phpunit/php-file-iterator": "^2.0.5 || ^3.0.6", + "phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20", + "squizlabs/php_codesniffer": "^3.4" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-3.x": "3.x-dev", + "dev-master": "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": "2023-06-20T18:29:04+00:00" + }, + { + "name": "nette/schema", + "version": "v1.2.3", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "abbdbb70e0245d5f3bf77874cea1dfb0c930d06f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/abbdbb70e0245d5f3bf77874cea1dfb0c930d06f", + "reference": "abbdbb70e0245d5f3bf77874cea1dfb0c930d06f", + "shasum": "" + }, + "require": { + "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", + "php": ">=7.1 <8.3" + }, + "require-dev": { + "nette/tester": "^2.3 || ^2.4", + "phpstan/phpstan-nette": "^1.0", + "tracy/tracy": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.2.3" + }, + "time": "2022-10-13T01:24:26+00:00" + }, + { + "name": "nette/utils", + "version": "v4.0.0", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "cacdbf5a91a657ede665c541eda28941d4b09c1e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/cacdbf5a91a657ede665c541eda28941d4b09c1e", + "reference": "cacdbf5a91a657ede665c541eda28941d4b09c1e", + "shasum": "" + }, + "require": { + "php": ">=8.0 <8.3" + }, + "conflict": { + "nette/finder": "<3", + "nette/schema": "<1.2.2" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "dev-master", + "nette/tester": "^2.4", + "phpstan/phpstan": "^1.0", + "tracy/tracy": "^2.9" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", + "ext-xml": "to use Strings::length() etc. when mbstring is not available" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v4.0.0" + }, + "time": "2023-02-02T10:41:53+00:00" + }, + { + "name": "nunomaduro/termwind", + "version": "v1.15.1", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/termwind.git", + "reference": "8ab0b32c8caa4a2e09700ea32925441385e4a5dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/8ab0b32c8caa4a2e09700ea32925441385e4a5dc", + "reference": "8ab0b32c8caa4a2e09700ea32925441385e4a5dc", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^8.0", + "symfony/console": "^5.3.0|^6.0.0" + }, + "require-dev": { + "ergebnis/phpstan-rules": "^1.0.", + "illuminate/console": "^8.0|^9.0", + "illuminate/support": "^8.0|^9.0", + "laravel/pint": "^1.0.0", + "pestphp/pest": "^1.21.0", + "pestphp/pest-plugin-mock": "^1.0", + "phpstan/phpstan": "^1.4.6", + "phpstan/phpstan-strict-rules": "^1.1.0", + "symfony/var-dumper": "^5.2.7|^6.0.0", + "thecodingmachine/phpstan-strict-rules": "^1.0.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Termwind\\Laravel\\TermwindServiceProvider" + ] + } + }, + "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/v1.15.1" + }, + "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": "2023-02-08T01:06:31+00:00" + }, + { + "name": "opis/json-schema", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/opis/json-schema.git", + "reference": "c48df6d7089a45f01e1c82432348f2d5976f9bfb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/json-schema/zipball/c48df6d7089a45f01e1c82432348f2d5976f9bfb", + "reference": "c48df6d7089a45f01e1c82432348f2d5976f9bfb", + "shasum": "" + }, + "require": { + "ext-json": "*", + "opis/string": "^2.0", + "opis/uri": "^1.0", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "ext-bcmath": "*", + "ext-intl": "*", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Opis\\JsonSchema\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + }, + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + } + ], + "description": "Json Schema Validator for PHP", + "homepage": "https://opis.io/json-schema", + "keywords": [ + "json", + "json-schema", + "schema", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/opis/json-schema/issues", + "source": "https://github.com/opis/json-schema/tree/2.3.0" + }, + "time": "2022-01-08T20:38:03+00:00" + }, + { + "name": "opis/string", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/opis/string.git", + "reference": "9ebf1a1f873f502f6859d11210b25a4bf5d141e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/string/zipball/9ebf1a1f873f502f6859d11210b25a4bf5d141e7", + "reference": "9ebf1a1f873f502f6859d11210b25a4bf5d141e7", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "ext-json": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Opis\\String\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "Multibyte strings as objects", + "homepage": "https://opis.io/string", + "keywords": [ + "multi-byte", + "opis", + "string", + "string manipulation", + "utf-8" + ], + "support": { + "issues": "https://github.com/opis/string/issues", + "source": "https://github.com/opis/string/tree/2.0.1" + }, + "time": "2022-01-14T15:42:23+00:00" + }, + { + "name": "opis/uri", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/opis/uri.git", + "reference": "0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/uri/zipball/0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a", + "reference": "0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a", + "shasum": "" + }, + "require": { + "opis/string": "^2.0", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Opis\\Uri\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "Build, parse and validate URIs and URI-templates", + "homepage": "https://opis.io", + "keywords": [ + "URI Template", + "parse url", + "punycode", + "uri", + "uri components", + "url", + "validate uri" + ], + "support": { + "issues": "https://github.com/opis/uri/issues", + "source": "https://github.com/opis/uri/tree/1.1.0" + }, + "time": "2021-05-22T15:57:08+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "dd3a383e599f49777d8b628dadbb90cae435b87e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dd3a383e599f49777d8b628dadbb90cae435b87e", + "reference": "dd3a383e599f49777d8b628dadbb90cae435b87e", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": true + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2023-02-25T19:38:58+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "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/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "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" + }, + "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": "psr/simple-cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "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/3.0.0" + }, + "time": "2021-10-29T13:26:27+00:00" + }, + { + "name": "ramsey/collection", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", + "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.28.3", + "fakerphp/faker": "^1.21", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^1.0", + "mockery/mockery": "^1.5", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpcsstandards/phpcsutils": "^1.0.0-rc1", + "phpspec/prophecy-phpunit": "^2.0", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5", + "psalm/plugin-mockery": "^1.1", + "psalm/plugin-phpunit": "^0.18.4", + "ramsey/coding-standard": "^2.0.3", + "ramsey/conventional-commits": "^1.3", + "vimeo/psalm": "^5.4" + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.0.0" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", + "type": "tidelift" + } + ], + "time": "2022-12-31T21:50:55+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.7.4", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "60a4c63ab724854332900504274f6150ff26d286" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/60a4c63ab724854332900504274f6150ff26d286", + "reference": "60a4c63ab724854332900504274f6150ff26d286", + "shasum": "" + }, + "require": { + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11", + "ext-json": "*", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.10", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "doctrine/annotations": "^1.8", + "ergebnis/composer-normalize": "^2.15", + "mockery/mockery": "^1.3", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.2", + "php-mock/php-mock-mockery": "^1.3", + "php-parallel-lint/php-parallel-lint": "^1.1", + "phpbench/phpbench": "^1.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^8.5 || ^9", + "ramsey/composer-repl": "^1.4", + "slevomat/coding-standard": "^8.4", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.9" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.7.4" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", + "type": "tidelift" + } + ], + "time": "2023-04-15T23:01:58+00:00" + }, + { + "name": "symfony/console", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "8788808b07cf0bdd6e4b7fdd23d8ddb1470c83b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/8788808b07cf0bdd6e4b7fdd23d8ddb1470c83b7", + "reference": "8788808b07cf0bdd6e4b7fdd23d8ddb1470c83b7", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^5.4|^6.0" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/lock": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.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/v6.3.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": "2023-05-29T12:49:39+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "88453e64cd86c5b60e8d2fb2c6f953bbc353ffbf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/88453e64cd86c5b60e8d2fb2c6f953bbc353ffbf", + "reference": "88453e64cd86c5b60e8d2fb2c6f953bbc353ffbf", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Converts CSS selectors to XPath expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/css-selector/tree/v6.3.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": "2023-03-20T16:43:42+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-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.3.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": "2023-05-23T14:45:45+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "99d2d814a6351461af350ead4d963bd67451236f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/99d2d814a6351461af350ead4d963bd67451236f", + "reference": "99d2d814a6351461af350ead4d963bd67451236f", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^5.4|^6.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5" + }, + "require-dev": { + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/serializer": "^5.4|^6.0" + }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "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 manage errors and ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/error-handler/tree/v6.3.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": "2023-05-10T12:03:13+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa", + "reference": "3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/error-handler": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "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 that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v6.3.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": "2023-04-21T14:41:17+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/a76aed96a42d2b521153fb382d418e30d18b59df", + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "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 dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.3.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": "2023-05-23T14:45:45+00:00" + }, + { + "name": "symfony/finder", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "d9b01ba073c44cef617c7907ce2419f8d00d75e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/d9b01ba073c44cef617c7907ce2419f8d00d75e2", + "reference": "d9b01ba073c44cef617c7907ce2419f8d00d75e2", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "symfony/filesystem": "^6.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/v6.3.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": "2023-04-02T01:25:41+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v6.3.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "e0ad0d153e1c20069250986cd9e9dd1ccebb0d66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e0ad0d153e1c20069250986cd9e9dd1ccebb0d66", + "reference": "e0ad0d153e1c20069250986cd9e9dd1ccebb0d66", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php83": "^1.27" + }, + "conflict": { + "symfony/cache": "<6.2" + }, + "require-dev": { + "doctrine/dbal": "^2.13.1|^3.0", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", + "symfony/mime": "^5.4|^6.0", + "symfony/rate-limiter": "^5.2|^6.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/v6.3.1" + }, + "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": "2023-06-24T11:51:27+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v6.3.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "161e16fd2e35fb4881a43bc8b383dfd5be4ac374" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/161e16fd2e35fb4881a43bc8b383dfd5be4ac374", + "reference": "161e16fd2e35fb4881a43bc8b383dfd5be4ac374", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.3", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/http-foundation": "^6.2.7", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/browser-kit": "<5.4", + "symfony/cache": "<5.4", + "symfony/config": "<6.1", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<6.3", + "symfony/doctrine-bridge": "<5.4", + "symfony/form": "<5.4", + "symfony/http-client": "<5.4", + "symfony/http-client-contracts": "<2.5", + "symfony/mailer": "<5.4", + "symfony/messenger": "<5.4", + "symfony/translation": "<5.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<5.4", + "symfony/validator": "<5.4", + "symfony/var-dumper": "<6.3", + "twig/twig": "<2.13" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^5.4|^6.0", + "symfony/clock": "^6.2", + "symfony/config": "^6.1", + "symfony/console": "^5.4|^6.0", + "symfony/css-selector": "^5.4|^6.0", + "symfony/dependency-injection": "^6.3", + "symfony/dom-crawler": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^5.4|^6.0", + "symfony/property-access": "^5.4.5|^6.0.5", + "symfony/routing": "^5.4|^6.0", + "symfony/serializer": "^6.3", + "symfony/stopwatch": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^5.4|^6.0", + "symfony/validator": "^6.3", + "symfony/var-exporter": "^6.2", + "twig/twig": "^2.13|^3.0.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "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 a structured process for converting a Request into a Response", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-kernel/tree/v6.3.1" + }, + "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": "2023-06-26T06:07:32+00:00" + }, + { + "name": "symfony/mailer", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/mailer.git", + "reference": "7b03d9be1dea29bfec0a6c7b603f5072a4c97435" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mailer/zipball/7b03d9be1dea29bfec0a6c7b603f5072a4c97435", + "reference": "7b03d9be1dea29bfec0a6c7b603f5072a4c97435", + "shasum": "" + }, + "require": { + "egulias/email-validator": "^2.1.10|^3|^4", + "php": ">=8.1", + "psr/event-dispatcher": "^1", + "psr/log": "^1|^2|^3", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/mime": "^6.2", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<5.4", + "symfony/messenger": "<6.2", + "symfony/mime": "<6.2", + "symfony/twig-bridge": "<6.2.1" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/messenger": "^6.2", + "symfony/twig-bridge": "^6.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mailer\\": "" + }, + "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": "Helps sending emails", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/mailer/tree/v6.3.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": "2023-05-29T12:49:39+00:00" + }, + { + "name": "symfony/mime", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "7b5d2121858cd6efbed778abce9cfdd7ab1f62ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/7b5d2121858cd6efbed778abce9cfdd7ab1f62ad", + "reference": "7b5d2121858cd6efbed778abce9cfdd7ab1f62ad", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "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": "<5.4", + "symfony/serializer": "<6.2" + }, + "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": "^5.4|^6.0", + "symfony/property-access": "^5.4|^6.0", + "symfony/property-info": "^5.4|^6.0", + "symfony/serializer": "^6.2" + }, + "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/v6.3.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": "2023-04-28T15:57:00+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "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.27.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": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "511a08c03c1960e08a883f4cffcacd219b758354" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", + "reference": "511a08c03c1960e08a883f4cffcacd219b758354", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "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.27.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": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "639084e360537a19f9ee352433b84ce831f3d2da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/639084e360537a19f9ee352433b84ce831f3d2da", + "reference": "639084e360537a19f9ee352433b84ce831f3d2da", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "symfony/polyfill-intl-normalizer": "^1.10", + "symfony/polyfill-php72": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "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.27.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": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "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.27.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": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "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/v1.27.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": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "869329b1e9894268a8a61dabb69153029b7a8c97" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/869329b1e9894268a8a61dabb69153029b7a8c97", + "reference": "869329b1e9894268a8a61dabb69153029b7a8c97", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "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/v1.27.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": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.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": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "508c652ba3ccf69f8c97f251534f229791b52a57" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/508c652ba3ccf69f8c97f251534f229791b52a57", + "reference": "508c652ba3ccf69f8c97f251534f229791b52a57", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "symfony/polyfill-php80": "^1.14" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + } + }, + "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/v1.27.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": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-uuid", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "f3cf1a645c2734236ed1e2e671e273eeb3586166" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/f3cf1a645c2734236ed1e2e671e273eeb3586166", + "reference": "f3cf1a645c2734236ed1e2e671e273eeb3586166", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-uuid": "*" + }, + "suggest": { + "ext-uuid": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Uuid\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for uuid functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.27.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": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/process", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "8741e3ed7fe2e91ec099e02446fb86667a0f1628" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/8741e3ed7fe2e91ec099e02446fb86667a0f1628", + "reference": "8741e3ed7fe2e91ec099e02446fb86667a0f1628", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "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/v6.3.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": "2023-05-19T08:06:44+00:00" + }, + { + "name": "symfony/routing", + "version": "v6.3.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "d37ad1779c38b8eb71996d17dc13030dcb7f9cf5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/d37ad1779c38b8eb71996d17dc13030dcb7f9cf5", + "reference": "d37ad1779c38b8eb71996d17dc13030dcb7f9cf5", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "conflict": { + "doctrine/annotations": "<1.12", + "symfony/config": "<6.2", + "symfony/dependency-injection": "<5.4", + "symfony/yaml": "<5.4" + }, + "require-dev": { + "doctrine/annotations": "^1.12|^2", + "psr/log": "^1|^2|^3", + "symfony/config": "^6.2", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "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": "Maps an HTTP request to a set of configuration variables", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/v6.3.1" + }, + "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": "2023-06-05T15:30:22+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", + "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^2.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-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.3.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": "2023-05-23T14:45:45+00:00" + }, + { + "name": "symfony/string", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "f2e190ee75ff0f5eced645ec0be5c66fac81f51f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/f2e190ee75ff0f5eced645ec0be5c66fac81f51f", + "reference": "f2e190ee75ff0f5eced645ec0be5c66fac81f51f", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "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/error-handler": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/intl": "^6.2", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^5.4|^6.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/v6.3.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": "2023-03-21T21:06:29+00:00" + }, + { + "name": "symfony/translation", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "f72b2cba8f79dd9d536f534f76874b58ad37876f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/f72b2cba8f79dd9d536f534f76874b58ad37876f", + "reference": "f72b2cba8f79dd9d536f534f76874b58ad37876f", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^2.5|^3.0" + }, + "conflict": { + "symfony/config": "<5.4", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<5.4", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<5.4", + "symfony/service-contracts": "<2.5", + "symfony/twig-bundle": "<5.4", + "symfony/yaml": "<5.4" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13", + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/intl": "^5.4|^6.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^5.4|^6.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^5.4|^6.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/v6.3.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": "2023-05-19T12:46:45+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "02c24deb352fb0d79db5486c0c79905a85e37e86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/02c24deb352fb0d79db5486c0c79905a85e37e86", + "reference": "02c24deb352fb0d79db5486c0c79905a85e37e86", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-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.3.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": "2023-05-30T17:17:10+00:00" + }, + { + "name": "symfony/uid", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/uid.git", + "reference": "01b0f20b1351d997711c56f1638f7a8c3061e384" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/uid/zipball/01b0f20b1351d997711c56f1638f7a8c3061e384", + "reference": "01b0f20b1351d997711c56f1638f7a8c3061e384", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-uuid": "^1.15" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Uid\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to generate and represent UIDs", + "homepage": "https://symfony.com", + "keywords": [ + "UID", + "ulid", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/uid/tree/v6.3.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": "2023-04-08T07:25:02+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.3.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "c81268d6960ddb47af17391a27d222bd58cf0515" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/c81268d6960ddb47af17391a27d222bd58cf0515", + "reference": "c81268d6960ddb47af17391a27d222bd58cf0515", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "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 mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.3.1" + }, + "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": "2023-06-21T12:08:28+00:00" + }, + { + "name": "tijsverkoyen/css-to-inline-styles", + "version": "2.2.6", + "source": { + "type": "git", + "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", + "reference": "c42125b83a4fa63b187fdf29f9c93cb7733da30c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/c42125b83a4fa63b187fdf29f9c93cb7733da30c", + "reference": "c42125b83a4fa63b187fdf29f9c93cb7733da30c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "php": "^5.5 || ^7.0 || ^8.0", + "symfony/css-selector": "^2.7 || ^3.0 || ^4.0 || ^5.0 || ^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^7.5 || ^8.5.21 || ^9.5.10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "TijsVerkoyen\\CssToInlineStyles\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Tijs Verkoyen", + "email": "css_to_inline_styles@verkoyen.eu", + "role": "Developer" + } + ], + "description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.", + "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", + "support": { + "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/2.2.6" + }, + "time": "2023-01-03T09:29:04+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.5.0", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7", + "reference": "1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.0.2", + "php": "^7.1.3 || ^8.0", + "phpoption/phpoption": "^1.8", + "symfony/polyfill-ctype": "^1.23", + "symfony/polyfill-mbstring": "^1.23.1", + "symfony/polyfill-php80": "^1.23.1" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-filter": "*", + "phpunit/phpunit": "^7.5.20 || ^8.5.30 || ^9.5.25" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": true + }, + "branch-alias": { + "dev-master": "5.5-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.5.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2022-10-16T01:01:54+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" + }, + { + "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" + } + ], + "packages-dev": [ + { + "name": "brianium/paratest", + "version": "v6.10.0", + "source": { + "type": "git", + "url": "https://github.com/paratestphp/paratest.git", + "reference": "c2243b20bcd99c3f651018d1447144372f39b4fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/c2243b20bcd99c3f651018d1447144372f39b4fa", + "reference": "c2243b20bcd99c3f651018d1447144372f39b4fa", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-simplexml": "*", + "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1", + "jean85/pretty-package-versions": "^2.0.5", + "php": "^7.3 || ^8.0", + "phpunit/php-code-coverage": "^9.2.25", + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-timer": "^5.0.3", + "phpunit/phpunit": "^9.6.4", + "sebastian/environment": "^5.1.5", + "symfony/console": "^5.4.21 || ^6.2.7", + "symfony/process": "^5.4.21 || ^6.2.7" + }, + "require-dev": { + "doctrine/coding-standard": "^10.0.0", + "ext-pcov": "*", + "ext-posix": "*", + "infection/infection": "^0.26.19", + "squizlabs/php_codesniffer": "^3.7.2", + "symfony/filesystem": "^5.4.21 || ^6.2.7", + "vimeo/psalm": "^5.7.7" + }, + "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/v6.10.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/Slamdunk", + "type": "github" + }, + { + "url": "https://paypal.me/filippotessarotto", + "type": "paypal" + } + ], + "time": "2023-05-25T13:47:58+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^11", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" + }, + "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%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-12-30T00:23:10+00:00" + }, + { + "name": "fakerphp/faker", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/FakerPHP/Faker.git", + "reference": "e3daa170d00fde61ea7719ef47bb09bb8f1d9b01" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e3daa170d00fde61ea7719ef47bb09bb8f1d9b01", + "reference": "e3daa170d00fde61ea7719ef47bb09bb8f1d9b01", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "conflict": { + "fzaninotto/faker": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "doctrine/persistence": "^1.3 || ^2.0", + "ext-intl": "*", + "phpunit/phpunit": "^9.5.26", + "symfony/phpunit-bridge": "^5.4.16" + }, + "suggest": { + "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", + "ext-curl": "Required by Faker\\Provider\\Image to download images.", + "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", + "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", + "ext-mbstring": "Required for multibyte Unicode string functionality." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "v1.21-dev" + } + }, + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "support": { + "issues": "https://github.com/FakerPHP/Faker/issues", + "source": "https://github.com/FakerPHP/Faker/tree/v1.23.0" + }, + "time": "2023-06-12T08:44:38+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "0.5.1", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "b58e5a3933e541dc286cc91fc4f3898bbc6f1623" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/b58e5a3933e541dc286cc91fc4f3898bbc6f1623", + "reference": "b58e5a3933e541dc286cc91fc4f3898bbc6f1623", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "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": "^9.5.26 || ^8.5.31", + "theofidry/php-cs-fixer-config": "^1.0", + "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/0.5.1" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2022-12-24T12:35:10+00:00" + }, + { + "name": "filp/whoops", + "version": "2.15.2", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "aac9304c5ed61bf7b1b7a6064bf9806ab842ce73" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/aac9304c5ed61bf7b1b7a6064bf9806ab842ce73", + "reference": "aac9304c5ed61bf7b1b7a6064bf9806ab842ce73", + "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.2" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2023-04-12T12:00:00+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.5.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "b635f279edd83fc275f822a1188157ffea568ff6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/b635f279edd83fc275f822a1188157ffea568ff6", + "reference": "b635f279edd83fc275f822a1188157ffea568ff6", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.29 || ^9.5.23" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.5.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2023-04-17T16:11:26+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "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", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" + }, + "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/v2.0.1" + }, + "time": "2020-07-09T08:09:16+00:00" + }, + { + "name": "jean85/pretty-package-versions", + "version": "2.0.5", + "source": { + "type": "git", + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/ae547e455a3d8babd07b96966b17d7fd21d9c6af", + "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.0.0", + "php": "^7.1|^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.17", + "jean85/composer-provided-replaced-stub-package": "^1.0", + "phpstan/phpstan": "^0.12.66", + "phpunit/phpunit": "^7.5|^8.5|^9.4", + "vimeo/psalm": "^4.3" + }, + "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.0.5" + }, + "time": "2021-10-08T21:21:46+00:00" + }, + { + "name": "mockery/mockery", + "version": "1.6.2", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "13a7fa2642c76c58fa2806ef7f565344c817a191" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/13a7fa2642c76c58fa2806ef7f565344c817a191", + "reference": "13a7fa2642c76c58fa2806ef7f565344c817a191", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "^2.0.1", + "lib-pcre": ">=7.0", + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5 || ^9.3", + "psalm/plugin-phpunit": "^0.18", + "vimeo/psalm": "^5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.6.x-dev" + } + }, + "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": "http://blog.astrumfutura.com" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "http://davedevelopment.co.uk" + } + ], + "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": { + "issues": "https://github.com/mockery/mockery/issues", + "source": "https://github.com/mockery/mockery/tree/1.6.2" + }, + "time": "2023-06-07T09:07:52+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.11.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "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", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "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.11.1" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2023-03-08T13:26:56+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.16.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "19526a33fb561ef417e822e85f08a00db4059c17" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/19526a33fb561ef417e822e85f08a00db4059c17", + "reference": "19526a33fb561ef417e822e85f08a00db4059c17", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-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/v4.16.0" + }, + "time": "2023-06-25T14:52:30+00:00" + }, + { + "name": "nunomaduro/collision", + "version": "v6.4.0", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/collision.git", + "reference": "f05978827b9343cba381ca05b8c7deee346b6015" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/f05978827b9343cba381ca05b8c7deee346b6015", + "reference": "f05978827b9343cba381ca05b8c7deee346b6015", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.14.5", + "php": "^8.0.0", + "symfony/console": "^6.0.2" + }, + "require-dev": { + "brianium/paratest": "^6.4.1", + "laravel/framework": "^9.26.1", + "laravel/pint": "^1.1.1", + "nunomaduro/larastan": "^1.0.3", + "nunomaduro/mock-final-classes": "^1.1.0", + "orchestra/testbench": "^7.7", + "phpunit/phpunit": "^9.5.23", + "spatie/ignition": "^1.4.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-develop": "6.x-dev" + }, + "laravel": { + "providers": [ + "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" + ] + } + }, + "autoload": { + "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": "2023-01-03T12:54:54+00:00" + }, + { + "name": "orchestra/testbench", + "version": "v7.25.0", + "source": { + "type": "git", + "url": "https://github.com/orchestral/testbench.git", + "reference": "35956b9a678c95fd1cb73769e94c5699237864c2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/orchestral/testbench/zipball/35956b9a678c95fd1cb73769e94c5699237864c2", + "reference": "35956b9a678c95fd1cb73769e94c5699237864c2", + "shasum": "" + }, + "require": { + "fakerphp/faker": "^1.21", + "laravel/framework": "^9.52.9", + "mockery/mockery": "^1.5.1", + "orchestra/testbench-core": "^7.25", + "php": "^8.0", + "phpunit/phpunit": "^9.5.10", + "spatie/laravel-ray": "^1.32.4", + "symfony/process": "^6.0.9", + "symfony/yaml": "^6.0.9", + "vlucas/phpdotenv": "^5.4.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.0-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mior Muhammad Zaki", + "email": "crynobone@gmail.com", + "homepage": "https://github.com/crynobone" + } + ], + "description": "Laravel Testing Helper for Packages Development", + "homepage": "https://packages.tools/testbench/", + "keywords": [ + "BDD", + "TDD", + "dev", + "laravel", + "laravel-packages", + "testing" + ], + "support": { + "issues": "https://github.com/orchestral/testbench/issues", + "source": "https://github.com/orchestral/testbench/tree/v7.25.0" + }, + "time": "2023-06-13T06:00:26+00:00" + }, + { + "name": "orchestra/testbench-core", + "version": "v7.25.0", + "source": { + "type": "git", + "url": "https://github.com/orchestral/testbench-core.git", + "reference": "d5da4c5733ede2c39658adedc45fa74e15f8c957" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/orchestral/testbench-core/zipball/d5da4c5733ede2c39658adedc45fa74e15f8c957", + "reference": "d5da4c5733ede2c39658adedc45fa74e15f8c957", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "fakerphp/faker": "^1.21", + "laravel/framework": "^9.52.9", + "laravel/pint": "^1.4", + "mockery/mockery": "^1.5.1", + "phpstan/phpstan": "^1.10.7", + "phpunit/phpunit": "^9.5.10", + "spatie/laravel-ray": "^1.32.4", + "symfony/process": "^6.0.9", + "symfony/yaml": "^6.0.9", + "vlucas/phpdotenv": "^5.4.1" + }, + "suggest": { + "brianium/paratest": "Allow using parallel tresting (^6.4).", + "fakerphp/faker": "Allow using Faker for testing (^1.21).", + "laravel/framework": "Required for testing (^9.52.9).", + "mockery/mockery": "Allow using Mockery for testing (^1.5.1).", + "nunomaduro/collision": "Allow using Laravel style tests output and parallel testing (^6.2).", + "orchestra/testbench-browser-kit": "Allow using legacy Laravel BrowserKit for testing (^7.0).", + "orchestra/testbench-dusk": "Allow using Laravel Dusk for testing (^7.0).", + "phpunit/phpunit": "Allow using PHPUnit for testing (^9.5.10).", + "symfony/yaml": "Required for CLI Commander (^6.0.9).", + "vlucas/phpdotenv": "Required for CLI Commander (^5.4.1)." + }, + "bin": [ + "testbench" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.0-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Orchestra\\Testbench\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mior Muhammad Zaki", + "email": "crynobone@gmail.com", + "homepage": "https://github.com/crynobone" + } + ], + "description": "Testing Helper for Laravel Development", + "homepage": "https://packages.tools/testbench", + "keywords": [ + "BDD", + "TDD", + "dev", + "laravel", + "laravel-packages", + "testing" + ], + "support": { + "issues": "https://github.com/orchestral/testbench/issues", + "source": "https://github.com/orchestral/testbench-core" + }, + "time": "2023-06-13T05:37:48+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "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.3" + }, + "time": "2021-07-20T11:28:43+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": "phpstan/phpstan", + "version": "1.10.25", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "578f4e70d117f9a90699324c555922800ac38d8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/578f4e70d117f9a90699324c555922800ac38d8c", + "reference": "578f4e70d117f9a90699324c555922800ac38d8c", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2023-07-06T12:11:37+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.26", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", + "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.15", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "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-master": "9.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 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", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-03-06T12:58:08+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "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": "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", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.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": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "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": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.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", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.6.10", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "a6d351645c3fe5a30f5e86be6577d946af65a328" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a6d351645c3fe5a30f5e86be6577d946af65a328", + "reference": "a6d351645c3fe5a30f5e86be6577d946af65a328", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1 || ^2", + "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": ">=7.3", + "phpunit/php-code-coverage": "^9.2.13", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.8", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.5", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^3.2", + "sebastian/version": "^3.0.2" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.6-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/9.6.10" + }, + "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": "2023-07-10T04:04:23+00:00" + }, + { + "name": "pimple/pimple", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "a94b3a4db7fb774b3d78dad2315ddc07629e1bed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a94b3a4db7fb774b3d78dad2315ddc07629e1bed", + "reference": "a94b3a4db7fb774b3d78dad2315ddc07629e1bed", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1 || ^2.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^5.4@dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4.x-dev" + } + }, + "autoload": { + "psr-0": { + "Pimple": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Pimple, a simple Dependency Injection Container", + "homepage": "https://pimple.symfony.com", + "keywords": [ + "container", + "dependency injection" + ], + "support": { + "source": "https://github.com/silexphp/Pimple/tree/v3.5.0" + }, + "time": "2021-10-28T11:13:42+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "e616d01114759c4c489f93b099585439f795fe35" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", + "reference": "e616d01114759c4c489f93b099585439f795fe35", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/1.0.2" + }, + "time": "2023-04-10T20:10:41+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.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", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:08:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.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", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.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", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.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", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T12:41:17+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.7", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "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 calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:52:27+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-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", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-05-07T05:35:17+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.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": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:03:51+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.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": "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", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T06:03:37+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "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": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-02-14T08:28:10+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.6", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.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", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-28T06:42:11+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.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", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.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", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.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", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:07:39+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "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": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:45:17+00:00" + }, + { + "name": "sebastian/type", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "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": "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", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:13:03+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "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": "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", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "spatie/backtrace", + "version": "1.5.3", + "source": { + "type": "git", + "url": "https://github.com/spatie/backtrace.git", + "reference": "483f76a82964a0431aa836b6ed0edde0c248e3ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/483f76a82964a0431aa836b6ed0edde0c248e3ab", + "reference": "483f76a82964a0431aa836b6ed0edde0c248e3ab", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "ext-json": "*", + "phpunit/phpunit": "^9.3", + "spatie/phpunit-snapshot-assertions": "^4.2", + "symfony/var-dumper": "^5.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Backtrace\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van de Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "A better backtrace", + "homepage": "https://github.com/spatie/backtrace", + "keywords": [ + "Backtrace", + "spatie" + ], + "support": { + "source": "https://github.com/spatie/backtrace/tree/1.5.3" + }, + "funding": [ + { + "url": "https://github.com/sponsors/spatie", + "type": "github" + }, + { + "url": "https://spatie.be/open-source/support-us", + "type": "other" + } + ], + "time": "2023-06-28T12:59:17+00:00" + }, + { + "name": "spatie/laravel-ray", + "version": "1.32.5", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-ray.git", + "reference": "288f30c94c9725dfd78d8a1b82b230521f4d697e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-ray/zipball/288f30c94c9725dfd78d8a1b82b230521f4d697e", + "reference": "288f30c94c9725dfd78d8a1b82b230521f4d697e", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/contracts": "^7.20|^8.19|^9.0|^10.0", + "illuminate/database": "^7.20|^8.19|^9.0|^10.0", + "illuminate/queue": "^7.20|^8.19|^9.0|^10.0", + "illuminate/support": "^7.20|^8.19|^9.0|^10.0", + "php": "^7.4|^8.0", + "spatie/backtrace": "^1.0", + "spatie/ray": "^1.37", + "symfony/stopwatch": "4.2|^5.1|^6.0", + "zbateson/mail-mime-parser": "^1.3.1|^2.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.3", + "laravel/framework": "^7.20|^8.19|^9.0|^10.0", + "orchestra/testbench-core": "^5.0|^6.0|^7.0|^8.0", + "pestphp/pest": "^1.22", + "phpstan/phpstan": "^0.12.93", + "phpunit/phpunit": "^9.3", + "spatie/pest-plugin-snapshots": "^1.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.29.x-dev" + }, + "laravel": { + "providers": [ + "Spatie\\LaravelRay\\RayServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\LaravelRay\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Easily debug Laravel apps", + "homepage": "https://github.com/spatie/laravel-ray", + "keywords": [ + "laravel-ray", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-ray/issues", + "source": "https://github.com/spatie/laravel-ray/tree/1.32.5" + }, + "funding": [ + { + "url": "https://github.com/sponsors/spatie", + "type": "github" + }, + { + "url": "https://spatie.be/open-source/support-us", + "type": "other" + } + ], + "time": "2023-06-23T07:04:32+00:00" + }, + { + "name": "spatie/macroable", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/macroable.git", + "reference": "ec2c320f932e730607aff8052c44183cf3ecb072" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/macroable/zipball/ec2c320f932e730607aff8052c44183cf3ecb072", + "reference": "ec2c320f932e730607aff8052c44183cf3ecb072", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.0|^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Macroable\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "A trait to dynamically add methods to a class", + "homepage": "https://github.com/spatie/macroable", + "keywords": [ + "macroable", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/macroable/issues", + "source": "https://github.com/spatie/macroable/tree/2.0.0" + }, + "time": "2021-03-26T22:39:02+00:00" + }, + { + "name": "spatie/ray", + "version": "1.37.2", + "source": { + "type": "git", + "url": "https://github.com/spatie/ray.git", + "reference": "dea16182d4bc9d9833adec7e39fbb3d7b553425d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/ray/zipball/dea16182d4bc9d9833adec7e39fbb3d7b553425d", + "reference": "dea16182d4bc9d9833adec7e39fbb3d7b553425d", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "php": "^7.3|^8.0", + "ramsey/uuid": "^3.0|^4.1", + "spatie/backtrace": "^1.1", + "spatie/macroable": "^1.0|^2.0", + "symfony/stopwatch": "^4.0|^5.1|^6.0", + "symfony/var-dumper": "^4.2|^5.1|^6.0" + }, + "require-dev": { + "illuminate/support": "6.x|^8.18|^9.0", + "nesbot/carbon": "^2.63", + "pestphp/pest": "^1.22", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5", + "spatie/phpunit-snapshot-assertions": "^4.2", + "spatie/test-time": "^1.2" + }, + "type": "library", + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\Ray\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Debug with Ray to fix problems faster", + "homepage": "https://github.com/spatie/ray", + "keywords": [ + "ray", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/ray/issues", + "source": "https://github.com/spatie/ray/tree/1.37.2" + }, + "funding": [ + { + "url": "https://github.com/sponsors/spatie", + "type": "github" + }, + { + "url": "https://spatie.be/open-source/support-us", + "type": "other" + } + ], + "time": "2023-05-17T06:35:47+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.7.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879", + "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2023-02-22T23:07:41+00:00" + }, + { + "name": "symfony/polyfill-iconv", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-iconv.git", + "reference": "927013f3aac555983a5059aada98e1907d842695" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/927013f3aac555983a5059aada98e1907d842695", + "reference": "927013f3aac555983a5059aada98e1907d842695", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-iconv": "*" + }, + "suggest": { + "ext-iconv": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Iconv\\": "" + } + }, + "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 Iconv extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "iconv", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-iconv/tree/v1.27.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": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2", + "reference": "fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/service-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "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 a way to profile code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v6.3.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": "2023-02-16T10:14:28+00:00" + }, + { + "name": "symfony/yaml", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "a9a8337aa641ef2aa39c3e028f9107ec391e5927" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/a9a8337aa641ef2aa39c3e028f9107ec391e5927", + "reference": "a9a8337aa641ef2aa39c3e028f9107ec391e5927", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<5.4" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "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": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v6.3.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": "2023-04-28T13:28:14+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "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.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2021-07-28T10:34:58+00:00" + }, + { + "name": "zbateson/mail-mime-parser", + "version": "2.4.0", + "source": { + "type": "git", + "url": "https://github.com/zbateson/mail-mime-parser.git", + "reference": "20b3e48eb799537683780bc8782fbbe9bc25934a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zbateson/mail-mime-parser/zipball/20b3e48eb799537683780bc8782fbbe9bc25934a", + "reference": "20b3e48eb799537683780bc8782fbbe9bc25934a", + "shasum": "" + }, + "require": { + "guzzlehttp/psr7": "^1.7.0|^2.0", + "php": ">=7.1", + "pimple/pimple": "^3.0", + "zbateson/mb-wrapper": "^1.0.1", + "zbateson/stream-decorators": "^1.0.6" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "*", + "mikey179/vfsstream": "^1.6.0", + "phpstan/phpstan": "*", + "phpunit/phpunit": "<10" + }, + "suggest": { + "ext-iconv": "For best support/performance", + "ext-mbstring": "For best support/performance" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZBateson\\MailMimeParser\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Zaahid Bateson" + }, + { + "name": "Contributors", + "homepage": "https://github.com/zbateson/mail-mime-parser/graphs/contributors" + } + ], + "description": "MIME email message parser", + "homepage": "https://mail-mime-parser.org", + "keywords": [ + "MimeMailParser", + "email", + "mail", + "mailparse", + "mime", + "mimeparse", + "parser", + "php-imap" + ], + "support": { + "docs": "https://mail-mime-parser.org/#usage-guide", + "issues": "https://github.com/zbateson/mail-mime-parser/issues", + "source": "https://github.com/zbateson/mail-mime-parser" + }, + "funding": [ + { + "url": "https://github.com/zbateson", + "type": "github" + } + ], + "time": "2023-02-14T22:58:03+00:00" + }, + { + "name": "zbateson/mb-wrapper", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/zbateson/mb-wrapper.git", + "reference": "faf35dddfacfc5d4d5f9210143eafd7a7fe74334" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zbateson/mb-wrapper/zipball/faf35dddfacfc5d4d5f9210143eafd7a7fe74334", + "reference": "faf35dddfacfc5d4d5f9210143eafd7a7fe74334", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "symfony/polyfill-iconv": "^1.9", + "symfony/polyfill-mbstring": "^1.9" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "*", + "phpstan/phpstan": "*", + "phpunit/phpunit": "<=9.0" + }, + "suggest": { + "ext-iconv": "For best support/performance", + "ext-mbstring": "For best support/performance" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZBateson\\MbWrapper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Zaahid Bateson" + } + ], + "description": "Wrapper for mbstring with fallback to iconv for encoding conversion and string manipulation", + "keywords": [ + "charset", + "encoding", + "http", + "iconv", + "mail", + "mb", + "mb_convert_encoding", + "mbstring", + "mime", + "multibyte", + "string" + ], + "support": { + "issues": "https://github.com/zbateson/mb-wrapper/issues", + "source": "https://github.com/zbateson/mb-wrapper/tree/1.2.0" + }, + "funding": [ + { + "url": "https://github.com/zbateson", + "type": "github" + } + ], + "time": "2023-01-11T23:05:44+00:00" + }, + { + "name": "zbateson/stream-decorators", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/zbateson/stream-decorators.git", + "reference": "783b034024fda8eafa19675fb2552f8654d3a3e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zbateson/stream-decorators/zipball/783b034024fda8eafa19675fb2552f8654d3a3e9", + "reference": "783b034024fda8eafa19675fb2552f8654d3a3e9", + "shasum": "" + }, + "require": { + "guzzlehttp/psr7": "^1.9 | ^2.0", + "php": ">=7.2", + "zbateson/mb-wrapper": "^1.0.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "*", + "phpstan/phpstan": "*", + "phpunit/phpunit": "<10.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZBateson\\StreamDecorators\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Zaahid Bateson" + } + ], + "description": "PHP psr7 stream decorators for mime message part streams", + "keywords": [ + "base64", + "charset", + "decorators", + "mail", + "mime", + "psr7", + "quoted-printable", + "stream", + "uuencode" + ], + "support": { + "issues": "https://github.com/zbateson/stream-decorators/issues", + "source": "https://github.com/zbateson/stream-decorators/tree/1.2.1" + }, + "funding": [ + { + "url": "https://github.com/zbateson", + "type": "github" + } + ], + "time": "2023-05-30T22:51:52+00:00" + } + ], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": [], + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "ext-json": "*", + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/database/Factories/Tests/Mocks/Models/VehicleFactory.php b/database/Factories/Tests/Mocks/Models/VehicleFactory.php new file mode 100644 index 0000000..0a86978 --- /dev/null +++ b/database/Factories/Tests/Mocks/Models/VehicleFactory.php @@ -0,0 +1,18 @@ + '11111111111111111' + ]; + } +} \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..8a69054 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,21 @@ + + + + + ./tests/Unit + + + + + ./app + + + + + + + diff --git a/tests/BaseTestCase.php b/tests/BaseTestCase.php new file mode 100644 index 0000000..9806448 --- /dev/null +++ b/tests/BaseTestCase.php @@ -0,0 +1,55 @@ +app['events']); + } + + protected function tearDown(): void + { + parent::tearDown(); + JsonModel::clearBootedModels(); + } + + /** + * Given two things that support json encoding, + * assert that they are identical in their canonicalized (sorted props), stringified form + * @param mixed $a + * @param mixed $b + */ + public static function assertCanonicallySame($a, $b, string $comment = ''): void + { + $cannonA = Json::canonicalize(json_encode($a), JSON_PRETTY_PRINT); + $cannonB = Json::canonicalize(json_encode($b), JSON_PRETTY_PRINT); + self::assertSame($cannonA, $cannonB, $comment); + } + + /** + * Define environment setup. + * + * @param \Illuminate\Foundation\Application $app + * @return void + */ + protected function defineEnvironment($app) + { + // Setup default database to use sqlite :memory: + $app['config']->set('json-schema.base_url', 'https://schemas.dealerinspire.com/online-shopper/'); + $app['config']->set('json-schema.local_base_prefix', dirname(__FILE__) . '/Schemas'); + $app['config']->set('json-schema.local_base_prefix_tests', dirname(__FILE__) . '/Schemas'); + $app['config']->set('database.connections.testbench', [ + 'driver' => 'sqlite', + 'database' => ':memory:', + 'prefix' => '', + ]); + } +} \ No newline at end of file diff --git a/tests/CustomMockInterface.php b/tests/CustomMockInterface.php new file mode 100644 index 0000000..747e28c --- /dev/null +++ b/tests/CustomMockInterface.php @@ -0,0 +1,23 @@ + 0, + 'saved_fired' => 0, + 'creating_fired' => 0, + 'created_fired' => 0, + 'deleted_fired' => 0, + ]; + + /** Note, no one cares what saved and created and deleted return */ + + /** + * Wire up all the observers with the simple boot method (instead of a separated Observer class) + */ + public static function boot() + { + static::saving(function (EventedJsonModel $model) { + $model->saving_fired += 1; + return $model->saving_returns; + }); + + static::saved(function (EventedJsonModel $model) { + $model->saved_fired += 1; + }); + + static::creating(function (EventedJsonModel $model) { + $model->creating_fired += 1; + return $model->creating_returns; + }); + + static::created(function (EventedJsonModel $model) { + $model->created_fired += 1; + }); + + static::deleting(function (EventedJsonModel $model) { + $model->deleting_fired += 1; + return $model->deleting_returns; + }); + + static::deleting(function (EventedJsonModel $model) { + $model->deleted_fired += 1; + }); + + parent::boot(); + } +} diff --git a/tests/MockClasses/EventedModelWithJsonAttributes.php b/tests/MockClasses/EventedModelWithJsonAttributes.php new file mode 100644 index 0000000..c9c779b --- /dev/null +++ b/tests/MockClasses/EventedModelWithJsonAttributes.php @@ -0,0 +1,94 @@ + [EventedJsonModel::class, 'thing'], + 'things' => [EventedJsonModel::class, 'things', null, CollectionOfJsonModels::IS_A], + ]; + + /** @var bool What should the saving handler return? */ + public $saving_returns = true; + + /** @var bool What should the creating handler return? */ + public $creating_returns = true; + + /** @var bool What should the deleting handler return? */ + public $deleting_returns = true; + + protected $attributes = [ + 'saving_fired' => 0, + 'saved_fired' => 0, + 'creating_fired' => 0, + 'created_fired' => 0, + 'deleted_fired' => 0, + ]; + + /** Note, no one cares what saved and created and deleted return */ + + /** + * Wire up all the observers with the simple boot method (instead of a separated Observer class) + */ + public static function boot() + { + static::saving(function (self $model) { + $model->saving_fired += 1; + return $model->saving_returns; + }); + + static::saved(function (self $model) { + $model->saved_fired += 1; + }); + + static::creating(function (self $model) { + $model->creating_fired += 1; + return $model->creating_returns; + }); + + static::created(function (self $model) { + $model->created_fired += 1; + }); + + static::deleting(function (self $model) { + $model->deleting_fired += 1; + return $model->deleting_returns; + }); + + static::deleting(function (self $model) { + $model->deleted_fired += 1; + }); + + parent::boot(); + } + + // I'm not allowed to use the database, I have no table + protected function performInsert(Builder $query) + { + return true; + } + + protected function performUpdate(Builder $query) + { + return true; + } +} diff --git a/tests/MockClasses/Invokeable.php b/tests/MockClasses/Invokeable.php new file mode 100644 index 0000000..e33d7ba --- /dev/null +++ b/tests/MockClasses/Invokeable.php @@ -0,0 +1,26 @@ +shouldReceive('__invoke') + * ->once() + * ->with('some','args') + * ->andReturn(true); + */ + +declare(strict_types=1); + +namespace Tests\MockClasses; + +/** + * Class Invokeable + * @package Tests\MockClasses + */ +class Invokeable +{ + public function __invoke() + { + } +} diff --git a/tests/Mocks/Models/Vehicle.php b/tests/Mocks/Models/Vehicle.php new file mode 100644 index 0000000..0b19666 --- /dev/null +++ b/tests/Mocks/Models/Vehicle.php @@ -0,0 +1,13 @@ +setType(Vehicle::class); + $model = new class extends Model {}; + //Fill a model with an array of vehicle-shaped data (as assoc array) + $model->vehicles = [ + ['vin' => '11111111111111111'] + ]; + // Link it up + $collection->link($model, 'vehicles')->fresh(); + self::assertIsArray($model->vehicles[0]); + self::assertInstanceOf(Vehicle::class, $collection[0]); + } + + public function testCantFreshWithoutSetType() + { + $collection = new CollectionOfJsonModels(); + $model = new class extends Model {}; + //Fill a model with an array of vehicle-shaped data (as assoc array) + $model->vehicles = [ + Vehicle::factory() + ->make() + ->mugglify(), + ]; + // Link it up + $this->expectException(\DomainException::class); + $this->expectExceptionMessage("Can't load CollectionOfJsonModels until type has been set."); + $collection->link($model, 'vehicles')->fresh(); + } + + /** + * Build a model, initialized with the attribute 'data' set to the provided array, + * and a JsonModel linked to that Model and whole attribute + * @return array [Model, JsonModel] + */ + protected function modelAndCollectionOfVehicles(): array + { + $model = mock(Model::class)->makePartial(); + $model->vehicles = []; + + $collection = new CollectionOfJsonModels(); + $collection->setType(Vehicle::class); + $collection->link($model, 'vehicles'); + return [$model, $collection]; + } + + public function testSaveSetsAttributesAndCascadesSave() + { + [$model, $collection] = $this->modelAndCollectionOfVehicles(); + $model + ->shouldReceive('save') + ->with() + ->once() + ->andReturn(true); + $vehicle = Vehicle::factory()->make(); + $collection->push($vehicle)->save(); + self::assertNotNull($model->vehicles); + self::assertCanonicallySame($model->vehicles[0], $vehicle); + } + + public function testCollectionChangesWithoutSaveDontSaveModel() + { + [$model, $collection] = $this->modelAndCollectionOfVehicles(); + $model + ->shouldReceive('save') + ->never() + ->andReturn(true); + $vehicle = Vehicle::factory()->make(); + $collection->push($vehicle); // Push, no ->save() + } + + public function testCantValidateWithoutItemSchema() + { + $collection = new CollectionOfJsonModels(); + $this->expectException(\DomainException::class); + $this->expectExceptionMessage("Can't validate a CollectionOfJsonModels until type has been set."); + $collection->validateOrThrow(); + } + + public function testEmptyCollectionIsValid() + { + $collection = new CollectionOfJsonModels(); + $collection->setType(Vehicle::class); + self::assertCount(0, $collection); + self::assertTrue($collection->validateOrThrow()); + } + + public function testValidationIdentifiesProblemElementAndIsRecursive() + { + $collection = new CollectionOfJsonModels(); + $collection->setType(Vehicle::class); + + // Healthy zero + $collection->push(Vehicle::factory()->make()); + + // Unhealthy one + $collection->push(Vehicle::factory()->make()); + $collection[1]->vin = 'notavin'; + + try { + $collection->validateOrThrow(); + } catch (JsonSchemaValidationException $e) { + self::assertSame('Collection Of Json Models contains invalid data!', $e->getMessage()); + self::assertCanonicallySame(['1.vin' => ['Minimum string length is 17, found 7']], $e->errors()); + } + } + + public function testInputIsCastOnPush() + { + $collection = new CollectionOfJsonModels(); + $collection->setType(Vehicle::class); + + $collection->push(['vin' => '11111111111111111']); + + self::assertInstanceOf(Vehicle::class, $collection[0]); + } + + public function testCantPushWithoutSetType() + { + $collection = new CollectionOfJsonModels(); + $this->expectException(\DomainException::class); + $this->expectExceptionMessage("Can't add items to CollectionOfJsonModels until type has been set."); + $collection->push('literally anything'); + } + + public function testPrimaryKeyIndexesCollection() + { + $collection = new CollectionOfJsonModels(); + $collection->setType(Vehicle::class)->setPrimaryKey('vin'); + + $vehicle = Vehicle::factory()->make(); + $collection->push($vehicle); + + self::assertArrayHasKey($vehicle->vin, $collection); + self::assertArrayNotHasKey(0, $collection); + self::assertCount(1, $collection); + self::assertSame($collection[$vehicle->vin], $vehicle); + self::assertSame($collection->first(), $vehicle); // Not numeric, but still first-able + + $second = Vehicle::factory()->make(['vin' => '11111111111111112']); + $collection->push($second); + + self::assertArrayHasKey($second->vin, $collection); + self::assertArrayNotHasKey(1, $collection); + self::assertCount(2, $collection); + self::assertSame($collection[$second->vin], $second); + self::assertSame($collection->last(), $second); + } + + public function testCanRemoveByPrimaryKey() + { + $collection = new CollectionOfJsonModels(); + $collection->setType(Vehicle::class)->setPrimaryKey('vin'); + + $vehicle = Vehicle::factory()->make(); + $collection->push($vehicle); + self::assertCount(1, $collection); + $collection->forget($vehicle->vin); + self::assertCount(0, $collection); + self::assertSame([], $collection->all()); + } + + public function testPushWithPrimaryKeyPreventsDuplicates() + { + $collection = new CollectionOfJsonModels(); + $collection->setType(Vehicle::class)->setPrimaryKey('vin'); + + $collection->push(['vin' => '11111111111111111', 'extra' => false]); + $collection->push(['vin' => '22222222222222222']); + self::assertCount(2, $collection); + self::assertFalse($collection->find('11111111111111111')->extra); + + $this->expectException(UniqueException::class); + $this->expectExceptionMessage("Collection can't contain duplicate vin 11111111111111111"); + + $collection->push(['vin' => '11111111111111111', 'extra' => true]); + } + + public function testPushWithoutPrimaryKeyAllowsDuplicates() + { + $collection = new CollectionOfJsonModels(); + $collection->setType(Vehicle::class); + + $collection->push(['vin' => '11111111111111111']); + $collection->push(['vin' => '22222222222222222']); + $collection->push(['vin' => '11111111111111111']); + + self::assertCount(3, $collection); + self::assertCount(2, $collection->where('vin', '11111111111111111')); + } + + public function testCantSetTypeExceptJsonModel() + { + $collection = new CollectionOfJsonModels(); + $this->expectException(\DomainException::class); + $this->expectExceptionMessage('CollectionOfJsonModels type must be a descendent of JsonModel'); + $collection->setType(\stdClass::class); + } + + /** + * Collection implementation of features like ->sortBy returns a new + * Collection instance, but that means our new private variables like + * $this->upstream_model won't be preserved, breaking ->isLinked + */ + public function testSortBreaksLink() + { + $collection = new CollectionOfJsonModels(); + $collection->setType(Vehicle::class); + + $collection->push(['vin' => '22222222222222222']); + $collection->push(['vin' => '11111111111111111']); + + $sorted = $collection->sortBy('vin'); + + self::assertInstanceOf(CollectionOfJsonModels::class, $sorted); + self::assertNotSame($sorted, $collection, 'sortBy returns a totally new object'); + self::assertFalse( + $sorted->isLinked(), + "object returned from sortBy doesn't inherit protected attributes from original", + ); + + // The sort itself worked though + self::assertSame('11111111111111111', $sorted->first()->vin); + self::assertSame('22222222222222222', $collection->first()->vin); + } + + public function testCantFindWithoutPrimaryKey() + { + $collection = new CollectionOfJsonModels(); + $this->expectException(\DomainException::class); + $this->expectExceptionMessage('Cannot use method find until primary key has been set.'); + $collection->find(1); + } + + public function testFindSucceeds() + { + $collection = new CollectionOfJsonModels(); + $collection->setType(Vehicle::class)->setPrimaryKey('vin'); + + $collection->push(['vin' => '22222222222222222']); + $collection->push(['vin' => '11111111111111111']); + + $found = $collection->find('11111111111111111'); + self::assertInstanceOf(Vehicle::class, $found); + self::assertSame('11111111111111111', $found->vin); + } + + public function testFindFails() + { + $collection = new CollectionOfJsonModels(); + $collection->setType(Vehicle::class)->setPrimaryKey('vin'); + + $collection->push(['vin' => '22222222222222222']); + $collection->push(['vin' => '11111111111111111']); + + $found = $collection->find('33333333333333333'); + self::assertNull($found); + } + + public function testFindOrFailSucceeds() + { + $collection = new CollectionOfJsonModels(); + $collection->setType(Vehicle::class)->setPrimaryKey('vin'); + + $collection->push(['vin' => '22222222222222222']); + $collection->push(['vin' => '11111111111111111']); + + $found = $collection->find('11111111111111111'); + self::assertInstanceOf(Vehicle::class, $found); + self::assertSame('11111111111111111', $found->vin); + } + + public function testFindOrFailFail() + { + $collection = new CollectionOfJsonModels(); + $collection->setType(Vehicle::class)->setPrimaryKey('vin'); + + $collection->push(['vin' => '22222222222222222']); + $collection->push(['vin' => '11111111111111111']); + + $this->expectException(ModelNotFoundException::class); + $this->expectExceptionMessage('No query results for model'); + $collection->findOrFail('99999999999999999'); + } + + public function testSaveCollectionPreventedByChildSavingListener() + { + $model = mock(Model::class)->makePartial(); + $jsonModel = new EventedJsonModel(); + $jsonModel->saving_returns = false; + + $collection = new CollectionOfJsonModels(); + $collection->setType(EventedJsonModel::class)->link($model, 'attribute'); + $collection->push($jsonModel); + + // Saves should be rejected by the observer and never reach the model + $model + ->shouldReceive('save') + ->never() + ->andReturn(false); + $status = $collection->save(); + self::assertFalse($status); + self::assertNull($model->saving_fired); + self::assertNull($model->saved_fired); + self::assertSame(1, $jsonModel->saving_fired); + self::assertSame(0, $jsonModel->saved_fired); + } + + public function testSaveCollectionCallsChildSavedAndSavingListener() + { + $model = new EventedModelWithJsonAttributes(); + + $collectionItem = new EventedJsonModel(); + $model->things->push($collectionItem); + self::assertSame(0, $collectionItem->saving_fired); + self::assertSame(0, $collectionItem->saved_fired); + self::assertSame(0, $model->saving_fired); + self::assertSame(0, $model->saved_fired); + + $status = $model->things->save(); + self::assertTrue($status); + self::assertSame(1, $collectionItem->saving_fired); + self::assertSame(1, $collectionItem->saved_fired); + self::assertSame(1, $model->saving_fired); + self::assertSame(1, $model->saved_fired); + } + + public function testSaveCollectionCallsChildCreatingAndCreatedListeners() + { + $model = new EventedModelWithJsonAttributes(); + $collectionItem = new EventedJsonModel(); + $model->things->push($collectionItem); + self::assertSame(0, $collectionItem->creating_fired); + self::assertSame(0, $collectionItem->created_fired); + + $status = $model->things->save(); + self::assertTrue($status); + self::assertSame(1, $collectionItem->creating_fired); + self::assertSame(1, $collectionItem->created_fired); + } + + public function testElementsThatExistWhenCollectionIsCreatedExist() + { + $model = mock(Model::class)->makePartial(); + $jsonModel = new class extends JsonModel {}; + $model->attribute = [['already' => 'exists']]; + $collection = new CollectionOfJsonModels(); + $collection + ->setType(get_class($jsonModel)) + ->link($model, 'attribute') + ->fresh(); + self::assertCount(1, $collection); + self::assertTrue($collection[0]->exists); + } + + public function testSaveCollectionSkipsCreatingListenerForExistingChildren() + { + $model = mock(Model::class)->makePartial(); + $model->attribute = [['already' => 'exists']]; + $collection = new CollectionOfJsonModels(); + $collection + ->setType(EventedJsonModel::class) + ->link($model, 'attribute') + ->fresh(); + $existingJsonModel = $collection->first(); + self::assertTrue($existingJsonModel->exists); + + $newJsonModel = new EventedJsonModel(); + $collection->push($newJsonModel); + self::assertFalse($newJsonModel->exists); + + $model + ->shouldReceive('save') + ->once() + ->andReturn(true); + + $status = $collection->save(); + self::assertTrue($status); + self::assertSame(1, $newJsonModel->creating_fired); + self::assertSame(1, $newJsonModel->created_fired); + self::assertSame(0, $existingJsonModel->creating_fired); + self::assertSame(0, $existingJsonModel->created_fired); + } + + public function testCascadeEventsToItems() + { + $model = mock(Model::class)->makePartial(); + $model + ->shouldReceive('save') + ->once() + ->andReturn(true); + $collection = new CollectionOfJsonModels(); + $collection->setType(EventedJsonModel::class); + $collection->link($model, 'data'); + + $first = new EventedJsonModel(); + $collection->push($first); + $second = new EventedJsonModel(); + $collection->push($second); + + // Saving on the *collection* cascades events down to items + $saveStatus = $collection->save(); + self::assertTrue($saveStatus); + + self::assertSame(1, $first->creating_fired); + self::assertSame(1, $first->saving_fired); + self::assertSame(1, $first->saved_fired); + self::assertSame(1, $first->created_fired); + + self::assertSame(1, $second->creating_fired); + self::assertSame(1, $second->saving_fired); + self::assertSame(1, $second->saved_fired); + self::assertSame(1, $second->created_fired); + } + + public function testCascadePreventsEventsToItems() + { + $model = mock(Model::class)->makePartial(); + $model + ->shouldReceive('save') + ->never() // Saving cancelled by child never gets back to model + ->andReturn(true); + $collection = new CollectionOfJsonModels(); + $collection->setType(EventedJsonModel::class); + $collection->link($model, 'data'); + + $first = new EventedJsonModel(); + $first->saving_returns = false; + $collection->push($first); + $second = new EventedJsonModel(); + $collection->push($second); + + // Saving on the *collection* cascades events down to items + $saveStatus = $collection->save(); + self::assertFalse($saveStatus); + + self::assertSame(1, $first->creating_fired); + self::assertSame(1, $first->saving_fired); + self::assertSame(0, $first->saved_fired, 'Saving failed, saved should not be called'); + self::assertSame(0, $first->created_fired, 'Saving failed, created should not be called'); + + self::assertSame( + 0, + $second->creating_fired, + 'Previous sibling failed, this sibling fires no observers at all.', + ); + self::assertSame(0, $second->saving_fired); + self::assertSame(0, $second->saved_fired); + self::assertSame(0, $second->created_fired); + } + + public function testFillCastsToCollectionType(): void + { + $collection = new CollectionOfJsonModels(); + $collection->setType(JsonModelVehicle::class)->setPrimaryKey('vin'); + $collection->fill([['vin' => '99999999999999999'], ['vin' => '88888888888888888']]); + self::assertSame(2, $collection->count()); + $collection->each(function ($item) { + self::assertInstanceOf(JsonModelVehicle::class, $item); + }); + self::assertNotNull($collection->find('99999999999999999')); + self::assertNotNull($collection->find('88888888888888888')); + } +} + +class JsonModelVehicle extends JsonModel +{ + public const SCHEMA = "vehicle.json"; +} diff --git a/tests/Unit/JsonModelTest.php b/tests/Unit/JsonModelTest.php new file mode 100644 index 0000000..c438851 --- /dev/null +++ b/tests/Unit/JsonModelTest.php @@ -0,0 +1,861 @@ +set('json-schema.base_url', 'https://schemas.dealerinspire.com/online-shopper/'); + $app['config']->set('json-schema.local_base_prefix', dirname(__FILE__) . '/../../tests/Schemas'); + $app['config']->set('json-schema.local_base_prefix_tests', dirname(__FILE__) . '/../../tests/Schemas'); + $app['config']->set('database.connections.testbench', [ + 'driver' => 'sqlite', + 'database' => ':memory:', + 'prefix' => '', + ]); + } + + public function testAttributesCanBeTestedWithIsset(): void + { + $jsonModel = new ConcreteJsonModel(); + // Actually not set + self::assertFalse(isset($jsonModel->foo)); + self::assertFalse(isset($jsonModel['foo'])); + self::assertFalse($jsonModel['foo'] ?? false); + self::assertFalse($jsonModel->foo ?? false); + + $jsonModel->foo = 'bar'; // Set object style + self::assertTrue(isset($jsonModel->foo)); + self::assertTrue(isset($jsonModel['foo'])); + self::assertSame('bar', $jsonModel['foo']); + self::assertSame('bar', $jsonModel->foo); + + $jsonModel['fizz'] = 'buzz'; // Set array style + self::assertTrue(isset($jsonModel->fizz)); + self::assertTrue(isset($jsonModel['fizz'])); + self::assertSame('buzz', $jsonModel['fizz']); + self::assertSame('buzz', $jsonModel->fizz); + } + + public function testObjectStyleAttributes(): void + { + $jsonModel = new ConcreteJsonModel(); + $jsonModel->something = 'yes'; //Set as object + self::assertEquals($jsonModel->something, 'yes'); //Get as object + self::assertEquals($jsonModel['something'], 'yes'); //Get as array + self::assertEquals($jsonModel->toArray(), ['something' => 'yes']); + self::assertEquals($jsonModel->toJSON(), '{"something":"yes"}'); + } + + public function testArrayStyleAttributes(): void + { + $jsonModel = new ConcreteJsonModel(); + $jsonModel['something'] = 'yes'; // Set as array + self::assertEquals($jsonModel->something, 'yes'); //Get as object + self::assertEquals($jsonModel['something'], 'yes'); //Get as array + self::assertTrue(isset($jsonModel['something'])); // offsetExists + self::assertEquals($jsonModel->toArray(), ['something' => 'yes']); + self::assertEquals($jsonModel->toJSON(), '{"something":"yes"}'); + } + + public function testArrayStyleUnset(): void + { + $jsonModel = new ConcreteJsonModel(['something' => 'yes']); + self::assertEquals('yes', $jsonModel['something']); // Is set + self::assertTrue(isset($jsonModel['something'])); // offsetExists + unset($jsonModel['something']); // Unset as array, uses offsetUnset + self::assertNull($jsonModel['something']); // No longer set + self::assertFalse(isset($jsonModel['something'])); // offsetExists + } + + public function testAttributeStyleUnset(): void + { + $jsonModel = new ConcreteJsonModel(['something' => 'yes']); + self::assertEquals('yes', $jsonModel->something); // Is set + self::assertTrue(isset($jsonModel->something)); // offsetExists + unset($jsonModel->something); // Unset as attribute, uses __unset which then uses offsetUnset + self::assertNull($jsonModel->something); // No longer set + self::assertFalse(isset($jsonModel->something)); // offsetExists + } + + public function testArrayLiteralConstructor(): void + { + $data = ['string' => 'puppies', 'number' => 42, 'boolean' => true]; + $jsonModel = new ConcreteJsonModel($data); + self::assertEquals($jsonModel->string, 'puppies'); + self::assertEquals($jsonModel->number, 42); + self::assertEquals($jsonModel->boolean, true); + self::assertEquals($jsonModel->toArray(), $data); + self::assertEquals($jsonModel->toJSON(), json_encode($data)); + self::assertFalse($jsonModel->isLinked()); + } + + public function testCasts(): void + { + $caster = new class extends JsonModel { + protected $casts = [ + 'int' => 'int', + 'float' => 'float', + 'string' => 'string', + 'bool' => 'bool', + ]; + }; + $caster->int = 123.4; + self::assertSame(123, $caster->int); + $caster->float = '123.4'; + self::assertSame(123.4, $caster->float); + $caster->string = 0; + self::assertSame('0', $caster->string); + $caster->bool = 1; + self::assertTrue($caster->bool); + } + + public function testConstructorFailsWithNonModel(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("JsonModel couldn't understand the construct signature"); + new ConcreteJsonModel(new stdClass(), 'data'); + } + + /** + * Build a model, initialized with the attribute 'data' set to the provided array, + * and a JsonModel linked to that Model and whole attribute + * @return array [Model, JsonModel] + */ + protected function modelAndWholeAttributeJsonModel(): array + { + $model = mock(Model::class)->makePartial(); + $model->data = ['a' => 1]; + + $jsonmodel = new class ($model, 'data') extends JsonModel {}; + return [$model, $jsonmodel]; + } + + /** + * Given a model that has a property 'data' that contains JSON data, + * Verify that we can instantiate a new JsonModel that loads that data. + */ + public function testJsonModelLinks(): void + { + [$model, $jsonmodel] = $this->modelAndWholeAttributeJsonModel(); + self::assertSame($model->data, $jsonmodel->toArray()); + self::assertTrue($jsonmodel->isLinked()); + } + + // When changing a JsonModel attribute, JsonModel mutates, but the change is NOT immediately posted up to the model. + public function testAttributeSetDoesntSaveToModel(): void + { + [$model, $jsonmodel] = $this->modelAndWholeAttributeJsonModel(); + $jsonmodel->a = 2; + self::assertSame(2, $jsonmodel->a); + self::assertSame(1, $model->data['a']); + } + + // When you ->save() the change in the JsonModel, it uses ->update on the Model + public function testAttributeSavesOnJsonModelSave(): void + { + [$model, $jsonmodel] = $this->modelAndWholeAttributeJsonModel(); + + $jsonmodel->a = 2; + self::assertSame(1, $model->data['a']); + $model + ->shouldReceive('save') + ->with() + ->once() + ->andReturn(true); + $saved = $jsonmodel->save(); + self::assertTrue($saved); + self::assertSame(2, $jsonmodel->a); + self::assertSame(2, $model->data['a']); + } + + // If you use ->update on the JsonModel, it immediately uses ->update on the model + public function testAttributeSavesOnJsonModelUpdate(): void + { + [$model, $jsonmodel] = $this->modelAndWholeAttributeJsonModel(); + $model + ->shouldReceive('save') + ->with() + ->once() + ->andReturn(true) + ->andSet('data', ['a' => 2]); + $updated = $jsonmodel->update(['a' => 2]); + self::assertTrue($updated); + self::assertSame(2, $jsonmodel->a); + self::assertSame(2, $model->data['a']); + } + + /** + * Build a Model, initialized with the attribute 'data' set to an associative array 2 levels deep + * and a JsonModel linked to that Model, attribute, and an array at the top depth + * This will be the more common case in Online Shopper, a JsonModel on a part of deal data, like $deal->data['vehicle'] + * @return array [Model, JsonModel] + */ + protected function modelAndPartialAttributeJsonModel(): array + { + $model = mock(Model::class)->makePartial(); + $model->data = [ + 'vehicle' => ['vin' => 1], + 'untouched' => ['something' => 'else'], + ]; + + $jsonmodel = new class ($model, 'data', 'vehicle') extends JsonModel {}; + return [$model, $jsonmodel]; + } + + /** + * Given a Model and a partial-attribute JsonModel + * Verify that the new JsonModel loads that data. + */ + public function testJsonModelPartialAttributeLinks(): void + { + [$model, $jsonmodel] = $this->modelAndPartialAttributeJsonModel(); + self::assertSame($model->data['vehicle'], $jsonmodel->toArray()); + self::assertTrue($jsonmodel->isLinked()); + } + + // When changing a JsonModel partial attribute, JsonModel mutates, but the change is NOT immediately posted up to the model. + public function testPartialAttributeSetDoesntSaveToModel(): void + { + [$model, $jsonmodel] = $this->modelAndPartialAttributeJsonModel(); + $jsonmodel->vin = 2; + self::assertSame(2, $jsonmodel->vin); + self::assertSame(1, $model->data['vehicle']['vin']); + } + + // When you ->save() the change in the JsonModel, it uses ->update on the Model + public function testPartialAttributeSavesOnJsonModelSave(): void + { + [$model, $jsonmodel] = $this->modelAndPartialAttributeJsonModel(); + + $jsonmodel->vin = 2; + self::assertSame(1, $model->data['vehicle']['vin']); + $model + ->shouldReceive('save') + ->once() + ->andReturn(true); + $saved = $jsonmodel->save(); + self::assertTrue($saved); + self::assertSame(2, $jsonmodel->vin); + self::assertSame(2, $model->data['vehicle']['vin']); + } + + // When you ->update() the change in the JsonModel, it uses ->update on the Model immediately + public function testPartialAttributeSavesOnJsonModelUpdate(): void + { + [$model, $jsonmodel] = $this->modelAndPartialAttributeJsonModel(); + + $model + ->shouldReceive('save') + ->with() + ->once() + ->andReturn(true); + $updated = $jsonmodel->update(['vin' => 2]); + self::assertTrue($updated); + self::assertSame(2, $jsonmodel->vin); + self::assertSame(2, $model->data['vehicle']['vin']); + } + + public function testUnlinkedJsonModelCantSave(): void + { + $unlinked = new ConcreteJsonModel(); + self::assertFalse($unlinked->isLinked()); + $this->expectException(DomainException::class); + $this->expectExceptionMessage("JsonModel isn't linked"); + $unlinked->save(); + } + + public function testUnlinkedJsonModelCantDelete(): void + { + $unlinked = new ConcreteJsonModel(); + self::assertFalse($unlinked->isLinked()); + $this->expectException(DomainException::class); + $this->expectExceptionMessage("JsonModel isn't linked"); + $unlinked->delete(); + } + + protected function mockLinkedValidatedJsonModel(): array + { + $model = mock(Model::class)->makePartial(); + $model->data = ['a' => 1]; + + $jsonmodel = new class ($model, 'data') extends JsonModel { + public const SCHEMA = '{ "properties":{ "a":{"type":"integer"} }, "required_params":["a"]}'; + }; + return [$model, $jsonmodel]; + } + + protected function mockLinkedValidatedJsonModelWithChildrenModel(): array + { + $model = mock(Model::class)->makePartial(); + $model->data = ['a' => ['aa' => 11, 'bb' => 22], 'b' => 2]; + + $jsonmodel = new class ($model, 'data') extends JsonModel { + use HasJsonModelAttributes; + + /** @var array */ + protected $jsonModelAttributes = [ + 'a' => [ConcreteJsonModel::class, 'a'], + ]; + + public const SCHEMA = '{"type":"object"}'; + }; + return [$model, $jsonmodel]; + } + + public function testRecursiveUpdateDoesNotCompletelyReplaceData(): void + { + /** @var $jsonmodel JsonModel */ + [$model, $jsonmodel] = $this->mockLinkedValidatedJsonModelWithChildrenModel(); + + $model + ->shouldReceive('save') + ->once() + ->with() + ->andReturn(true); + + $jsonmodel->updateRecursive(['a' => ['aa' => 33]]); + self::assertCanonicallySame( + ['a' => ['aa' => 33, 'bb' => 22], 'b' => 2], + $model->data + ); + } + + public function testRecursiveUpdateSavesEachChildGroupOnce(): void + { + /** @var $jsonmodel JsonModel */ + [$model, $jsonmodel] = $this->mockLinkedValidatedJsonModelWithChildrenModel(); + + $model + ->shouldReceive('save') + ->once() + ->with() + ->andReturn(true); + + $jsonmodel->updateRecursive([ + 'a' => [ + 'aa' => 'updated on child', + 'cc' => 'new on child', + ], + 'b' => 'updated on parent', + 'c' => 'new on parent', + ]); + self::assertEquals('updated on child', $model->data['a']['aa']); + self::assertEquals('new on child', $model->data['a']['cc']); + self::assertEquals('updated on parent', $model->data['b']); + self::assertEquals('new on parent', $model->data['c']); + } + + public function testLinkedJsonModelValidatesBeforeSave(): void + { + [$model, $jsonmodel] = $this->mockLinkedValidatedJsonModel(); + SchemaValidator::shouldReceive('validateOrThrow') + ->once() + ->with( + $jsonmodel, + $jsonmodel::SCHEMA, + 'Anonymous Descendent of Json Model contains invalid data!', + false, + 400, + ) + ->andReturn(true); + + $model + ->shouldReceive('save') + ->once() + ->with() + ->andReturn(true); + + $save_status = $jsonmodel->update(['a' => 2]); + self::assertTrue($save_status); + } + + public function testValidateWithUriSchema(): void + { + $jsonModel = new class (['vin' => '11111111111111111', 'make' => 'DeLorean', 'model' => 'DMC-12']) extends JsonModel { + public const SCHEMA = 'https://schemas.dealerinspire.com/online-shopper/vehicle.json'; + }; + self::assertTrue($jsonModel->validateOrThrow()); + } + + public function testLinkedJsonModelValidationHaltsSave(): void + { + [$model, $jsonmodel] = $this->mockLinkedValidatedJsonModel(); + $model + ->shouldReceive('update') + ->never() + ->andReturn(false); // If validation passes due to a future error, this gives you a clearer "should have never updated" error instead of "save returned null" + $this->expectException(JsonSchemaValidationException::class); + $this->expectExceptionMessage('Anonymous Descendent of Json Model contains invalid data!'); + + $jsonmodel->a = 'not_an_integer'; + + $jsonmodel->save(); + } + + /** + * This test verifies that if we register a saving observer that returns literally false + * the Save is aborted without ever reaching the Model + */ + public function testSavingObserverPreventsSave(): void + { + /** @var Model $model */ + $model = mock(Model::class)->makePartial(); + $jsonModel = new EventedJsonModel($model, 'data'); + $jsonModel->saving_returns = false; + // Saves should be rejected by the observer and never reach the model + $model + ->shouldReceive('save') + ->never() + ->andReturn(false); + $status = $jsonModel->save(); + self::assertFalse($status); + } + + /** + * Creating observer is called. + * It's called before saving. + * Then the save works, too + */ + public function testCreatingObserverOnNewJsonModelIsInvoked(): void + { + $model = mock(Model::class)->makePartial(); + // extend EventedJsonModel to explicitly test that creating was called before saving + $jsonModel = new class extends EventedJsonModel { + public static function boot(): void + { + static::saving(static function ($noob) { + if (!$noob->exists && !$noob->creating_fired) { + throw new Exception('Saving before creating'); + } + }); + parent::boot(); + } + }; + + // Saves should succeed + $model + ->shouldReceive('save') + ->once() + ->andReturn(true); + self::assertSame(0, $jsonModel->creating_fired); + self::assertSame(0, $jsonModel->saving_fired); + $jsonModel->link($model, 'data'); + $status = $jsonModel->save(); + self::assertTrue($status); + self::assertSame(1, $jsonModel->creating_fired); + self::assertSame(1, $jsonModel->saving_fired); + } + + public function testCreatingObserverFailsHaltsSave(): void + { + $model = mock(Model::class)->makePartial(); + $jsonModel = new EventedJsonModel(); + $jsonModel->creating_returns = false; + // Saves should be rejected by the observer and never reach the model + $model + ->shouldReceive('save') + ->never() + ->andReturn(false); + $jsonModel->link($model, 'data'); + $status = $jsonModel->save(); + self::assertFalse($status); + } + + public function testCreatingObserverSkippedForExistingJsonModel(): void + { + $model = mock(Model::class)->makePartial(); + $model->data = ['totally' => 'here']; + $jsonModel = new EventedJsonModel($model, 'data'); + self::assertTrue($jsonModel->exists); + $model + ->shouldReceive('save') + ->once() + ->andReturn(true); + $jsonModel->link($model, 'data'); + self::assertSame(0, $jsonModel->creating_fired); + self::assertSame(0, $jsonModel->created_fired); + self::assertSame(0, $jsonModel->saving_fired); + self::assertSame(0, $jsonModel->saved_fired); + $status = $jsonModel->save(); + self::assertTrue($status); + self::assertSame(0, $jsonModel->creating_fired); + self::assertSame(0, $jsonModel->created_fired); + self::assertSame(1, $jsonModel->saving_fired); + self::assertSame(1, $jsonModel->saved_fired); + } + + public function testCreatedAndCreatingFireOnlyOnce(): void + { + $model = mock(Model::class)->makePartial(); + // Note, passing $upstream model and $upstream_attribute in the constructor + $jsonModel = new EventedJsonModel(); + self::assertFalse($jsonModel->exists); + $model + ->shouldReceive('save') + ->twice() + ->andReturn(true); + $jsonModel->link($model, 'data'); + self::assertSame(0, $jsonModel->creating_fired); + self::assertSame(0, $jsonModel->created_fired); + self::assertSame(0, $jsonModel->saving_fired); + + $jsonModel->save(); + self::assertTrue($jsonModel->exists); + self::assertSame(1, $jsonModel->creating_fired); + self::assertSame(1, $jsonModel->created_fired); + self::assertSame(1, $jsonModel->saving_fired); + + // Save again, created&creating counters don't increment, saving does + $jsonModel->save(); + self::assertSame(1, $jsonModel->creating_fired); + self::assertSame(1, $jsonModel->created_fired); + self::assertSame(2, $jsonModel->saving_fired); + } + + /** + * This test verifies that if we register a deleting observer that returns literally false + * the Delete is aborted without ever reaching the Model + */ + public function testDeletingObserverPreventsDelete(): void + { + $model = mock(Model::class)->makePartial(); + $jsonModel = new EventedJsonModel($model, 'data'); + $jsonModel->deleting_returns = false; // Reject + // Saves should be rejected by the observer and never reach the model + $model + ->shouldReceive('save') + ->never() + ->andReturn(false); + $status = $jsonModel->delete(); + self::assertFalse($status); + } + + public function testImplementsJsonSerializableContract(): void + { + $jsonModel = new ConcreteJsonModel(['a' => 1]); + self::assertSame(json_encode($jsonModel), '{"a":1}'); + } + + public function testRecursionThrowsJsonEncodingException(): void + { + $jsonModel = new ConcreteJsonModel(['a' => 1]); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Cannot set a recursive property.'); + + $jsonModel->b = $jsonModel; + } + + + + public function testCantGetLinkedDataWhileNotLinked(): void + { + $jsonModel = new ConcreteJsonModel(['local' => 'data']); + $this->expectException(DomainException::class); + $this->expectExceptionMessage("JsonModel isn't linked"); + $jsonModel->getLinkedData(); + } + + public function testCantSetLinkedDataWhileNotLinked(): void + { + $jsonModel = new ConcreteJsonModel(['local' => 'data']); + $this->expectException(DomainException::class); + $this->expectExceptionMessage("JsonModel isn't linked"); + $jsonModel->setLinkedData(); + } + + public function testNewCollection(): void + { + $model = new ConcreteJsonModel(); + $collection = $model->newCollection(); + + self::assertInstanceOf(CollectionOfJsonModels::class, $collection); + self::assertEmpty($collection); + self::assertSame( + ConcreteJsonModel::class, + getProperty($collection, 'itemClass'), + "New collection's type is set to the creating models exact type", + ); + } + + public function testEmptyModelToJsonReturnsEmptyObject(): void + { + $model = new ConcreteJsonModel(); + + self::assertSame('{}', $model->toJson()); + } + + public function testIsEmptyReturnsTrueForEmptyObject(): void + { + $model = new ConcreteJsonModel(); + + self::assertTrue($model->isEmpty()); + } + + public function testIsLinkedToInstanceOfIsLinkedAndChildOf(): void + { + $model = new class extends ConcreteJsonModel {}; + $model->link(new ConcreteJsonModel(), 'test'); + + self::assertTrue($model->isLinkedToInstanceOf(ConcreteJsonModel::class)); + } + + public function testIsLinkedToInstanceOfIsNotLinkedButChildOf(): void + { + $model = new class extends ConcreteJsonModel { + public function __construct() + { + $this->upstream_model = new ConcreteJsonModel(); + } + }; + + self::assertFalse($model->isLinkedToInstanceOf(ConcreteJsonModel::class)); + } + + public function testIsLinkedToInstanceOfIsLinkedButNotChildOf(): void + { + $model = new class extends ConcreteJsonModel { + public function __construct() + { + $this->upstream_model = new UpstreamModel(); + $this->upstream_attribute = 'test'; + } + }; + + self::assertFalse($model->isLinkedToInstanceOf(ConcreteJsonModel::class)); + } + + public function testGetAncestorOfTypeIsNullOnNonLinkedModel(): void + { + $model = new ConcreteJsonModel(); + + $result = $model->getAncestorOfType(UpstreamModel::class); + self::assertNull($result); + } + + public function testGetAncestorOfTypeFindsParentModel(): void + { + $upstream = new UpstreamModel(); + $result = $upstream->child->getAncestorOfType(UpstreamModel::class); + self::assertSame($upstream, $result); + } + + public function testGetAncestorOfTypeFindsParentJsonModel(): void + { + $upstream = new UpstreamModel(); + $result = $upstream->child->grandchild->getAncestorOfType(DownstreamModel::class); + self::assertSame($upstream->child, $result); + } + + public function testGetAncestorOfTypeFindsClassRecursive(): void + { + $upstream = new UpstreamModel(); + $result = $upstream->child->grandchild->getAncestorOfType(UpstreamModel::class); + self::assertSame($upstream, $result); + } + + public function testHydratesChildrenAtCreate() + { + $upstream = new UpstreamModel(); + $attributeCache = getProperty($upstream, 'jsonModelAttributeCache'); + self::assertArrayHasKey('child', $attributeCache); + self::assertInstanceOf(DownstreamModel::class, $attributeCache['child']); + } + + public function testDoesNotHydrateChildrenEquivalentToNull() + { + $downstream = new DownstreamModel(); + $attributeCache = getProperty($downstream, 'jsonModelAttributeCache'); + self::assertArrayNotHasKey('anotherone', $attributeCache); + + $tradeinWithAnotherone = new DownstreamModel(['anotherone' => ['foo' => 100000]]); + $attributeCache = getProperty($tradeinWithAnotherone, 'jsonModelAttributeCache'); + self::assertArrayHasKey('anotherone', $attributeCache); + self::assertInstanceOf(NullableModel::class, $attributeCache['anotherone']); + } + + public function testCascadeEvents() + { + $model = mock(Model::class)->makePartial(); + $model + ->shouldReceive('save') + ->once() + ->andReturn(true); + $parent = new class ($model, 'data') extends EventedJsonModel { + use HasJsonModelAttributes; + protected $jsonModelAttributes = [ + 'child' => [EventedJsonModel::class, 'child'], + ]; + }; + + self::assertInstanceOf(EventedJsonModel::class, $parent->child); + + self::assertSame(0, $parent->saving_fired); + self::assertSame(0, $parent->saved_fired); + self::assertSame(0, $parent->child->saving_fired); + self::assertSame(0, $parent->child->saved_fired); + self::assertSame(0, $parent->child->creating_fired); + self::assertSame(0, $parent->child->created_fired); + + $parent->child->pizza = 'pepperoni'; + $saveStatus = $parent->save(); // Saving on the *parent* cascades events down to children + self::assertTrue($saveStatus); + self::assertSame(1, $parent->saving_fired); + self::assertSame(1, $parent->saved_fired); + self::assertSame(1, $parent->child->saving_fired); + self::assertSame(1, $parent->child->saved_fired); + self::assertSame(1, $parent->child->creating_fired); + self::assertSame(1, $parent->child->created_fired); + } + + public function testCascadePreventsEvents() + { + $model = mock(Model::class)->makePartial(); + $model + ->shouldReceive('save') + ->never() // Saving will be prevented by the child + ->andReturn(true); + $parent = new class ($model, 'data') extends EventedJsonModel { + use HasJsonModelAttributes; + protected $jsonModelAttributes = [ + 'child' => [EventedJsonModel::class, 'child'], + ]; + }; + + $parent->child->saving_returns = false; + + $parent->child->pizza = 'pepperoni'; + $saveStatus = $parent->save(); // Saving on the *parent* cascades events down to children + self::assertFalse($saveStatus); + self::assertSame(1, $parent->saving_fired, 'Parent saving observer should run before child'); + self::assertSame(0, $parent->saved_fired); + self::assertSame( + 1, + $parent->child->creating_fired, + 'Creating should be fired first, it is allowed, continues to saving', + ); + self::assertSame(1, $parent->child->saving_fired, 'Saving should be called, it returns false'); + self::assertSame(0, $parent->child->saved_fired, 'Saved should not be called'); + self::assertSame(0, $parent->child->created_fired, 'Created should not be called'); + } + + public function testCastToDatetime(): void + { + $model = new class extends JsonModel { + protected $casts = [ + 'field' => 'datetime', + ]; + }; + + // Set to carbon, internal rep is a reasonable string, and can retrieve as Carbon + $model->field = Carbon::parse('2021-01-01 01:01:01'); + // So weirdly, it uses a slightly different internal representation than what it encodes on wire and to disk. + // It's still the same logical time, and it still conforms to ISO-8601. + self::assertSame('2021-01-01T01:01:01+00:00', getProperty($model, 'attributes')['field']); + self::assertInstanceOf(Carbon::class, $model->field); + $modelString = $model->toJson(); + self::assertStringContainsString('"field":"2021-01-01T01:01:01.000000Z"', $modelString); + + // Set with any reasonable string, internal rep is a more precise string, and can retrieve as Carbon + $model->field = '2022-02-02 02:02:02'; + self::assertSame('2022-02-02T02:02:02+00:00', getProperty($model, 'attributes')['field']); + self::assertInstanceOf(Carbon::class, $model->field); + self::assertSame('2022-02-02T02:02:02.000000Z', $model->field->toJSON()); + } + + public function testSafeUpdate(): void + { + $mixedChanges = [ + 'email' => 'kaboom', + 'mobile_phone' => 'dontcall', + 'first_name' => 'Jeremy', + ]; + + $model = mock(Model::class)->makePartial(); + $jsonModel = new class ($model, 'data') extends EventedJsonModel { + const SCHEMA = "person.json"; + }; + $jsonModel->mobile_phone = '8885551111'; + + $model + ->shouldReceive('save') + ->once() + ->andReturn(true); + + $jsonModel->safeUpdate($mixedChanges); + self::assertFalse(isset($jsonModel->email)); // attribute is invalid, revert to unset + self::assertSame('8885551111', $jsonModel->mobile_phone); // attribute is invalid, revert to previous value + self::assertSame('Jeremy', $jsonModel->first_name); + } +} +/** + * These are classes and models needed to test inheritance, etc. + */ +class UpstreamModel extends JsonModel +{ + use HasJsonModelAttributes; + + /** @var array Json Model Attributes */ + protected $jsonModelAttributes = [ + 'child' => [DownstreamModel::class, 'child'], + ]; +} + +class DownstreamModel extends JsonModel +{ + use HasJsonModelAttributes; + + /** @var array Json Model Attributes */ + protected $jsonModelAttributes = [ + 'grandchild' => [BeyondDownstreamModel::class, 'grandchild'], + 'anotherone' => [NullableModel::class, 'anotherone'], + ]; +} + +class BeyondDownstreamModel extends JsonModel +{ +} + +class NullableModel extends JsonModel +{ + use NullWhenUsedAsAttributeWhenEmpty; +} diff --git a/tests/Unit/Traits/HasJsonModelAttributesTest.php b/tests/Unit/Traits/HasJsonModelAttributesTest.php new file mode 100644 index 0000000..7706ebe --- /dev/null +++ b/tests/Unit/Traits/HasJsonModelAttributesTest.php @@ -0,0 +1,425 @@ + ['a' => 1, 'b' => true]]) extends Model { + use HasJsonModelAttributes; + protected $guarded = []; + protected $casts = ['raw' => 'json']; + protected $jsonModelAttributes = ['jsonModel' => [ConcreteJsonModel::class, 'raw']]; + }; + self::assertInstanceOf(ConcreteJsonModel::class, $model->jsonModel); + self::assertSame(1, $model->jsonModel->a); + } + + public function testWholeAttributeMissingIsEmptyObject() + { + $model = new class() extends Model { + use HasJsonModelAttributes; + protected $guarded = []; + protected $casts = ['raw' => 'json']; + protected $jsonModelAttributes = ['jsonModel' => [ConcreteJsonModel::class, 'raw']]; + }; + // Missing is empty object + self::assertInstanceOf(ConcreteJsonModel::class, $model->jsonModel); + self::assertTrue($model->jsonModel->isEmpty()); + + $model->fill(['raw'=>null]); + + // Null is empty object + self::assertInstanceOf(ConcreteJsonModel::class, $model->jsonModel); + self::assertTrue($model->jsonModel->isEmpty()); + } + + public function testWholeAttributeMissingIsNull() + { + $model = new class() extends Model { + use HasJsonModelAttributes; + protected $guarded = []; + protected $casts = ['raw' => 'json']; + protected $jsonModelAttributes = ['jsonModel' => [ConcreteNullableJsonModel::class, 'raw']]; + }; + // Missing is null + self::assertNull($model->jsonModel); + + $model->fill(['raw'=>null]); + + // Null is null + self::assertNull($model->jsonModel); + } + + public function testPartialAttributeExists() + { + $model = new class(['container'=>['part'=>['a'=>1]]]) extends Model { + use HasJsonModelAttributes; + protected $guarded = []; + protected $casts = ['container' => 'json']; + protected $jsonModelAttributes = ['jsonModel' => [ConcreteJsonModel::class, 'container', 'part']]; + }; + self::assertInstanceOf(ConcreteJsonModel::class, $model->jsonModel); + self::assertSame(1, $model->jsonModel->a); + } + + public function testPartialAttributeMissingIsNull() + { + $model = new class() extends Model { + use HasJsonModelAttributes; + protected $guarded = []; + protected $casts = ['container' => 'json']; + protected $jsonModelAttributes = ['jsonModel' => [ConcreteNullableJsonModel::class, 'container', 'part']]; + }; + // Container missing is null + self::assertNull($model->jsonModel); + + // Container present, partial missing is null + $model->fill(['container'=>[]]); + self::assertNull($model->jsonModel); + + // Container present, partial null is null + $model->fill(['container'=>['part'=>null]]); + self::assertNull($model->jsonModel); + + // Container present, partial empty is null + $model->fill(['container'=>['part'=>[]]]); + self::assertNull($model->jsonModel); + } + + public function testPartialAttributeMissingIsEmptyObject() + { + $model = new class() extends Model { + use HasJsonModelAttributes; + protected $guarded = []; + protected $casts = ['container' => 'json']; + protected $jsonModelAttributes = ['jsonModel' => [ConcreteJsonModel::class, 'container', 'part']]; + }; + // Container missing is empty object + self::assertInstanceOf(ConcreteJsonModel::class, $model->jsonModel); + self::assertTrue($model->jsonModel->isEmpty()); + + // Container present, partial missing is empty object + $model->fill(['container'=>[]]); + self::assertInstanceOf(ConcreteJsonModel::class, $model->jsonModel); + self::assertTrue($model->jsonModel->isEmpty()); + + // Container present, partial null is empty object + $model->fill(['container'=>['part'=>null]]); + self::assertInstanceOf(ConcreteJsonModel::class, $model->jsonModel); + self::assertTrue($model->jsonModel->isEmpty()); + + // Container present, partial empty is empty object + $model->fill(['container'=>['part'=>[]]]); + self::assertInstanceOf(ConcreteJsonModel::class, $model->jsonModel); + self::assertTrue($model->jsonModel->isEmpty()); + } + + public function testJsonModelAttributesConfigMissing() + { + $model = new class() extends Model { + use HasJsonModelAttributes; + protected $guarded = []; + protected $casts = ['raw'=>'json']; + }; + $this->expectException(\DomainException::class); + $this->expectExceptionMessage(get_class($model) . " must define a property jsonModelAttributes to use HasJsonModelAttributes"); + $model->jsonModel; // any magic getter or setter will trigger isJsonModelAttribute + } + + public function testJsonModelAttributesConfigNotArray() + { + $model = new class() extends Model { + use HasJsonModelAttributes; + protected $guarded = []; + protected $casts = ['raw'=>'json']; + protected $jsonModelAttributes = 42; + }; + $this->expectException(\DomainException::class); + $this->expectExceptionMessage(get_class($model) . " must define an array for property jsonModelAttributes"); + $model->jsonModel; // any magic getter or setter will trigger isJsonModelAttribute + } + + public function testJsonModelAttributesConfigTooShort() + { + $model = new class() extends Model { + use HasJsonModelAttributes; + protected $guarded = []; + protected $casts = ['raw'=>'json']; + protected $jsonModelAttributes = ['jsonModel' => [ConcreteJsonModel::class]]; + }; + $this->expectException(\DomainException::class); + $this->expectExceptionMessage("Unusable jsonModelAttributes in " . get_class($model)); + $model->jsonModel; // any magic getter or setter will trigger isJsonModelAttribute + } + + public function testJsonModelAttributesConfigTooLong() + { + $model = new class() extends Model { + use HasJsonModelAttributes; + protected $guarded = []; + protected $casts = ['raw'=>'json']; + protected $jsonModelAttributes = [ + 'jsonModel' => [ + ConcreteJsonModel::class, + 'raw', + 'key', + CollectionOfJsonModels::NOT_A, + 'primary_key', + 'nonsense' + ]]; + }; + $this->expectException(\DomainException::class); + $this->expectExceptionMessage("Unusable jsonModelAttributes in " . get_class($model)); + $model->jsonModel; // any magic getter or setter will trigger isJsonModelAttribute + } + + public function testJsonModelAttributesConfigEntryNotArray() + { + $model = new class() extends Model { + use HasJsonModelAttributes; + protected $guarded = []; + protected $casts = ['raw' => 'json']; + protected $jsonModelAttributes = ['jsonModel' => ConcreteJsonModel::class]; + }; + $this->expectException(\DomainException::class); + $this->expectExceptionMessage("Unusable jsonModelAttributes in " . get_class($model)); + $model->jsonModel; // any magic getter or setter will trigger isJsonModelAttribute + } + + protected function modelWithWholeAttributeJsonModel() + { + return new class(['raw' => ['a' => 1, 'b' => true], 'nonJson' => 'Freddy' ]) extends Model { + use HasJsonModelAttributes; + + /** @var array $guarded let me ->fill() any attribute */ + protected $guarded = []; + + /** @var array $casts treat container as a JSON column */ + protected $casts = ['raw' => 'json']; + + /** @var array $jsonModelAttributes config for the HasJsonModelAttributes trait */ + protected $jsonModelAttributes = ['jsonModel' => [ConcreteJsonModel::class, 'raw']]; + }; + } + + public function testMagicGetDoesntInterfereWithNonJsonAttributes() + { + $model = $this->modelWithWholeAttributeJsonModel(); + self::assertSame('Freddy', $model->nonJson); + self::assertNull($model->completelyMissing); + } + + public function testMagicSetDoesntInterfereWithNonJsonAttributes() + { + $model = $this->modelWithWholeAttributeJsonModel(); + $model->nonJson = 'Chucky'; + self::assertSame('Chucky', $model->nonJson); + $model->completelyMissing = 'First'; + self::assertSame('First', $model->completelyMissing); + } + + + public function testSetWholeAttributeFromArray() + { + $model = $this->modelWithWholeAttributeJsonModel(); + $model->jsonModel = ['c'=>'3P0']; + self::assertSame(['c'=>'3P0'], $model->raw); // Overwriting everything on raw, a and b are lost + } + + public function testSetWholeAttributeFromObject() + { + $model = $this->modelWithWholeAttributeJsonModel(); + $model->jsonModel = new ConcreteJsonModel(['c'=>'3P0']); + self::assertSame(['c'=>'3P0'], $model->raw); // Overwriting everything on raw, a and b are lost + } + + public function testSetWholeAttributeNull() + { + $model = $this->modelWithWholeAttributeJsonModel(); + $this->expectExceptionObject(new InvalidArgumentException("jsonModel must be a Tests\MockClasses\ConcreteJsonModel or valid array")); + $model->jsonModel = null; + } + + public function testDeleteWholeAttribute(): void + { + $model = $this->modelWithWholeAttributeJsonModel(); + $model->jsonModel = ['first_name' => 'Zap', 'last_name' => 'Brannigan']; + self::assertArrayHasKey('raw', getProperty($model, 'attributes')); + self::assertStringContainsString('"jsonModel":', $model->toJson()); + + unset($model->jsonModel); + + self::assertArrayNotHasKey('raw', getProperty($model, 'attributes')); + self::assertStringNotContainsString('"jsonModel":', $model->toJson()); + } + + public function testSetWholeAttributeBadType() + { + $model = $this->modelWithWholeAttributeJsonModel(); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage("jsonModel must be a Tests\MockClasses\ConcreteJsonModel or valid array"); + $model->jsonModel = (object)['c'=>'3P0']; + } + + protected function modelWithPartialAttributeJsonModel() + { + return new class(['container' => ['part' => ['a' => 1]]]) extends Model { + use HasJsonModelAttributes; + + /** @var array $guarded let me ->fill() any attribute */ + protected $guarded = []; + + /** @var array $casts treat container as a JSON column */ + protected $casts = ['container' => 'json']; + + /** @var array $jsonModelAttributes config for the HasJsonModelAttributes trait */ + protected $jsonModelAttributes = ['jsonModel' => [ConcreteJsonModel::class, 'container', 'part']]; + }; + } + + public function testSetPartialAttributeFromArray() + { + $model = $this->modelWithPartialAttributeJsonModel(); + $model->jsonModel = ['c'=>'3P0']; + self::assertSame(['c'=>'3P0'], $model->container['part']); + } + + public function testSetPartialAttributeFromObject() + { + $model = $this->modelWithPartialAttributeJsonModel(); + $model->jsonModel = new ConcreteJsonModel(['c'=>'3P0']); + self::assertSame(['c'=>'3P0'], $model->container['part']); + } + + public function testSetPartialAttributeNull() + { + $model = $this->modelWithPartialAttributeJsonModel(); + + $this->expectExceptionObject(new InvalidArgumentException("jsonModel must be a Tests\MockClasses\ConcreteJsonModel or valid array")); + + $model->jsonModel = null; + } + + public function testDeletePartialAttribute(): void + { + $model = $this->modelWithPartialAttributeJsonModel(); + $model->jsonModel = ['first_name' => 'Zap', 'last_name' => 'Brannigan']; + self::assertArrayHasKey('container', getProperty($model, 'attributes')); + self::assertArrayHasKey('part', $model->container); + self::assertStringContainsString('"jsonModel":', $model->toJson()); + + unset($model->jsonModel); + + self::assertArrayHasKey('container', getProperty($model, 'attributes')); + self::assertArrayNotHasKey('part', $model->container); + self::assertStringNotContainsString('"jsonModel":', $model->toJson()); + } + + public function testSetPartialAttributeBadType() + { + $model = $this->modelWithPartialAttributeJsonModel(); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage("jsonModel must be a Tests\MockClasses\ConcreteJsonModel or valid array"); + $model->jsonModel = (object)['c'=>'3P0']; + } + + public function testJsonModelCollectionAsAttribute() + { + $model = new class() extends Model { + use HasJsonModelAttributes; + protected $guarded = []; + protected $casts = ['raw'=>'json']; + protected $jsonModelAttributes = [ + 'jsons' => [ + ConcreteJsonModel::class, + 'raw', + null, + CollectionOfJsonModels::IS_A, + ] + ]; + }; + self::assertInstanceOf(CollectionOfJsonModels::class, $model->jsons); + self::assertCount(0, $model->jsons); + } + + public function testJsonModelCollectionAsAttributeHonorsPrimaryKey() + { + $model = new class() extends Model { + use HasJsonModelAttributes; + protected $guarded = []; + protected $casts = ['raw'=>'json']; + protected $jsonModelAttributes = [ + 'jsons' => [ + ConcreteJsonModel::class, + 'raw', + null, + CollectionOfJsonModels::IS_A, + 'id' + ] + ]; + }; + self::assertInstanceOf(CollectionOfJsonModels::class, $model->jsons); + $model->jsons->push(['id'=>1, 'data'=>'first']); + $this->expectException(UniqueException::class); + $this->expectExceptionMessage("Collection can't contain duplicate id 1"); + $model->jsons->push(['id'=>1, 'data'=>'second']); + } + + public function testJsonModelCollectionHydratesChildrenToClass() + { + $model = new class(['raw' => [['id'=>1]]]) extends Model { + use HasJsonModelAttributes; + protected $guarded = []; + protected $casts = ['raw'=>'json']; + protected $jsonModelAttributes = [ + 'jsons' => [ + ConcreteJsonModel::class, + 'raw', + null, + CollectionOfJsonModels::IS_A, + 'id' + ] + ]; + }; + self::assertInstanceOf(CollectionOfJsonModels::class, $model->jsons); + self::assertCount(1, $model->jsons); + self::assertInstanceOf(ConcreteJsonModel::class, $model->jsons->first()); + self::assertSame(1, $model->jsons->first()->id); + } + + public function testAttributeCanBeTestedWithIsset() + { + $model = $this->modelWithWholeAttributeJsonModel(); + // Remember this is pre-populated in the method + self::assertSame(1, $model->jsonModel->a); + self::assertSame(1, $model->jsonModel->a ?? false); + self::assertFalse($model->jsonModel->other ?? false); + self::assertTrue(isset($model->jsonModel)); + + unset($model->jsonModel); + self::assertFalse(isset($model->jsonModel)); + self::assertFalse($model->jsonModel->a ?? false); + self::assertFalse($model->jsonModel->other ?? false); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..fbf462c --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,106 @@ +getMethod($methodName); + $method->setAccessible(true); + return empty($arguments) ? $method->invoke($object) : $method->invokeArgs($object, $arguments); + } +} + +if (!function_exists('callStaticMethod')) { + /** + * @param $class + * @param $methodName + * @param array $arguments + * @return mixed + * @throws ReflectionException + */ + function callStaticMethod($class, $methodName, array $arguments = []) + { + $class = new ReflectionClass($class); + $method = $class->getMethod($methodName); + $method->setAccessible(true); + return $method->invokeArgs(null, $arguments); + } +} + +if (!function_exists('getProperty')) { + /** + * Get protected or private property + * + * @param $object + * @param $propertyName + * @return mixed + * @throws ReflectionException + */ + function getProperty($object, $propertyName) + { + $reflection = new ReflectionClass($object); + $property = $reflection->getProperty($propertyName); + $property->setAccessible(true); + return $property->getValue($object); + } +} + +if (!function_exists('setProperty')) { + /** + * Set a protected or private property + * + * @param $object + * @param $propertyName + * @param $propertyValue + * @throws ReflectionException + */ + function setProperty($object, $propertyName, $propertyValue) + { + $reflectionClass = new ReflectionClass($object); + $property = $reflectionClass->getProperty($propertyName); + $property->setAccessible(true); + $property->setValue($object, $propertyValue); + $property->setAccessible(false); + } +} + +if (!function_exists('getConstant')) { + /** + * Get protected or private constant + */ + function getConstant($object, string $constantName) + { + $reflection = new ReflectionClass($object); + return $reflection->getConstant($constantName); + } +} From ee57bb321535d980c7f998ed46a2f3f23cc41e5e Mon Sep 17 00:00:00 2001 From: Jeremy Wadhams Date: Wed, 12 Jul 2023 15:17:14 -0500 Subject: [PATCH 2/2] License --- LICENSE | 21 +++++++++++++++++++++ composer.json | 7 +++++-- 2 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cf57e1c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Cars.com + +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. diff --git a/composer.json b/composer.json index 257c983..b5408f1 100644 --- a/composer.json +++ b/composer.json @@ -2,8 +2,11 @@ "name": "carsdotcom/laravel-json-model", "type": "library", "description": "Laravel models backed by JSON data", - "keywords": ["library", "laravel"], - "license": "UNLICENSED", + "keywords": [ + "library", + "laravel" + ], + "license": "MIT", "require": { "carsdotcom/laravel-json-schema": "^v1.0.1", "ext-json": "*",