diff --git a/.circleci/config.yml b/.circleci/config.yml index 30537ac..746699a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,7 +9,7 @@ executors: php-version: type: string default: "8.0" - working_directory: ~/laravel-super-cache-invalidate + working_directory: ~/laravel-super-cache jobs: test: diff --git a/.env.example b/.env.example index d57bfd1..9cf6766 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,3 @@ -SUPERCACHE_INVALIDATE_TOTAL_SHARDS=10 -SUPERCACHE_INVALIDATE_INVALIDATION_WINDOW=60 -SUPERCACHE_INVALIDATE_PROCESSING_LIMIT=10000 -SUPERCACHE_INVALIDATE_TAG_BATCH_SIZE=100 -SUPERCACHE_INVALIDATE_LOCK_TIMEOUT=600 -SUPERCACHE_INVALIDATE_KEY_INVALIDATION_CALLBACK= -SUPERCACHE_INVALIDATE_TAG_INVALIDATION_CALLBACK= +SUPERCACHE_PREFIX='supercache:' +SUPERCACHE_CONNECTION='default' +SUPERCACHE_NUM_SHARDS=256 diff --git a/CHANGELOG.md b/CHANGELOG.md index eab795a..06eff74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -All Notable changes to `laravel-super-cache-invalidate` will be documented in this file +All Notable changes to `laravel-uploadable` will be documented in this file -## 1.0.0 - 2024-11-20 +## 1.0.0 - 2024-10-03 - Initial release diff --git a/README.md b/README.md index b82e40e..0aeba87 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,54 @@ -# Laravel Super Cache Invalidate +# Laravel Super Cache -![Laravel Super Cache Invalidate](./resources/images/laravel-super-cache-invalidate-logo.webp) +![Laravel Super Cache](./resources/images/laravel-super-cache-logo.webp) -**Laravel Super Cache Invalidate** is a powerful package that provides an efficient and scalable cache invalidation system for Laravel applications. It is designed to handle high-throughput cache invalidation scenarios, such as those found in e-commerce platforms, by implementing advanced techniques like event queuing, coalescing, debouncing, sharding, and partitioning. +A powerful caching solution for Laravel that uses Redis with Lua scripting, batch processing, and optimized tag management to handle high volumes of keys efficiently. -[![Latest Version on Packagist](https://img.shields.io/packagist/v/padosoft/laravel-super-cache-invalidate.svg?style=flat-square)](https://packagist.org/packages/padosoft/laravel-super-cache-invalidate) +[![Latest Version on Packagist](https://img.shields.io/packagist/v/padosoft/laravel-super-cache.svg?style=flat-square)](https://packagist.org/packages/padosoft/laravel-super-cache) [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) -[![CircleCI](https://circleci.com/gh/padosoft/laravel-super-cache-invalidate.svg?style=shield)](https://circleci.com/gh/padosoft/laravel-super-cache-invalidate) -[![Quality Score](https://img.shields.io/scrutinizer/g/padosoft/laravel-super-cache-invalidate.svg?style=flat-square)](https://scrutinizer-ci.com/g/padosoft/laravel-super-cache-invalidate) -[![Total Downloads](https://img.shields.io/packagist/dt/padosoft/laravel-super-cache-invalidate.svg?style=flat-square)](https://packagist.org/packages/padosoft/laravel-super-cache-invalidate) +[![CircleCI](https://circleci.com/gh/padosoft/laravel-super-cache.svg?style=shield)](https://circleci.com/gh/padosoft/laravel-super-cache) +[![Quality Score](https://img.shields.io/scrutinizer/g/padosoft/laravel-super-cache.svg?style=flat-square)](https://scrutinizer-ci.com/g/padosoft/laravel-super-cache) +[![Total Downloads](https://img.shields.io/packagist/dt/padosoft/laravel-super-cache.svg?style=flat-square)](https://packagist.org/packages/padosoft/laravel-super-cache) ## Table of Contents -- [Introduction](#introduction) +- [Purpose](#purpose) +- [Why Use This Package?](#why-use-this-package) - [Features](#features) - [Requires](#requires) -- [Installation](#installation) -- [Configuration](#configuration) -- [Usage](#usage) - - [Inserting Invalidation Events](#inserting-invalidation-events) - - [Processing Invalidation Events](#processing-invalidation-events) - - [Pruning Old Data](#pruning-old-data) -- [Architecture and Techniques](#architecture-and-techniques) - - [Event Queue for Cache Invalidation](#event-queue-for-cache-invalidation) - - [Coalescing Invalidation Events](#coalescing-invalidation-events) - - [Debouncing Mechanism](#debouncing-mechanism) - - [Sharding and Parallel Processing](#sharding-and-parallel-processing) - - [Partitioning Tables](#partitioning-tables) - - [Semaphore Locking with Redis](#semaphore-locking-with-redis) -- [Performance Optimizations](#performance-optimizations) -- [Testing](#testing) -- [Contributing](#contributing) -- [License](#license) - -## Introduction - -In high-traffic applications, especially e-commerce platforms, managing cache invalidation efficiently is crucial. Frequent updates from various sources like ERP systems, warehouses, backoffice tools, and web orders can lead to performance bottlenecks if not handled properly. This package provides a robust solution by implementing advanced cache invalidation strategies. +- [Installation and Configuration](#installation-and-configuration) + - [Activating the Listener for Expiry Notifications](#activating-the-listener-for-expiry-notifications) +- [Usage Examples](#usage-examples) +- [Architecture Overview](#architecture-overview) +- [Design Decisions and Performance](#design-decisions-and-performance) + - [Key and Tag Organization](#key-and-tag-organization) + - [Sharding for Efficient Tag Management](#sharding-for-efficient-tag-management) + - [Locks for Concurrency Control](#locks-for-concurrency-control) + - [Handling Expire Correctly with TTLs](#handling-expire-correctly-with-ttls) + - [Namespace Suffix for Parallel Processing](#namespace-suffix-for-parallel-processing) + - [Scheduled Command for Orphaned Key Cleanup](#scheduled-command-for-orphaned-key-cleanup) + +## Purpose + +`laravel-super-cache` is designed to provide a high-performance, reliable, and scalable caching solution for Laravel applications that require efficient tag-based cache invalidation. By leveraging Redis's native capabilities and optimizing the way tags are managed, this package addresses limitations in Laravel's built-in cache tag system, making it suitable for large-scale enterprise applications. + +## Why Use This Package? + +Laravel's native caching mechanism has an implementation for tag-based cache management; however, it faces limitations, especially when dealing with high volumes of cache keys and frequent invalidations. Some of the issues include: + +1. **Inconsistency with Tag Invalidation**: Laravel uses a versioned tag strategy, which can lead to keys not being properly invalidated when associated tags change, particularly in highly concurrent environments. +2. **Performance Overhead**: The default implementation in Laravel relies on a "soft invalidate" mechanism that increments tag versions instead of directly removing keys, which can lead to slower cache operations and memory growth (memory leaks). +3. **Scalability Issues**: The handling of large volumes of tags and keys is not optimized for performance, making it difficult to maintain consistency and speed in enterprise-level applications. + +`laravel-super-cache` addresses these limitations by providing an efficient, high-performance caching layer optimized for Redis, leveraging Lua scripting, and batch processing techniques. ## Features -- **Asynchronous Event Queue**: Collect and process cache invalidation requests asynchronously. -- **Coalescing and Debouncing**: Merge multiple invalidation events and prevent redundant invalidations. -- **Sharding**: Distribute events across shards for parallel processing. -- **Partitioning**: Use MySQL partitioning for efficient data management and purging. -- **Semaphore Locking**: Prevent overlapping processes using Redis locks. -- **Customizable**: Configure invalidation windows, shard counts, batch sizes, and more. -- **High Performance**: Optimized for handling millions of events with minimal overhead. +- **Architecture for High Volume**: Designed to handle thousands of cache keys with rapid creation and deletion, without performance degradation. +- **Enterprise-Level Performance**: Uses Redis's native capabilities and optimizes cache storage and retrieval for high-speed operations. +- **Use of Lua Scripting for Efficiency**: Employs Lua scripts for atomic operations, reducing network round-trips and improving consistency. +- **Batch Processing and Pipelining**: Processes cache operations in batches, reducing overhead and maximizing throughput. +- **Parallel Processing with Namespaces**: Enables parallel processing of expiry notifications by using namespace suffixing and configurable listeners. ## Requires @@ -54,25 +57,306 @@ In high-traffic applications, especially e-commerce platforms, managing cache in - illuminate/support: ^9.0|^10.0|^11.0 -## Installation +## Installation and Configuration -Install the package via Composer: +To install `laravel-super-cache`, use Composer: ```bash -composer require padosoft/laravel-super-cache-invalidate +composer require padosoft/laravel-super-cache ``` -Publish the configuration and migrations: -```bash -php artisan vendor:publish --provider="Padosoft\SuperCacheInvalidate\SuperCacheInvalidationServiceProvider" +After installing the package, publish the configuration file: + +```bash +php artisan vendor:publish --provider="Padosoft\SuperCache\SuperCacheServiceProvider" ``` -Run migrations: +The configuration file allows you to set: + +- `prefix`: The prefix for all cache keys, preventing conflicts when using the same Redis instance. +- `connection`: The Redis connection to use, as defined in your `config/database.php`. +- `SUPERCACHE_NUM_SHARDS`: The number of shards to optimize performance for tag management. +- `retry_max`, `batch_size`, and `time_threshold`: Parameters to optimize batch processing and retries. + +### Activating the Listener for Expiry Notifications +The listener is responsible for handling expired cache keys and cleaning up associated tags. +To activate it, add the following command to your `supervisor` configuration: + +```ini +[program:supercache_listener] +command=php artisan supercache:listener {namespace} +numprocs=5 +``` + +You can run multiple processes in parallel by setting different {namespace} values. +This allows the listener to handle notifications in parallel for optimal performance. + +### Enabling Redis Expiry Notifications (required for listener) +To enable the expiry notifications required by `laravel-super-cache`, you need to configure Redis (or AWS ElastiCache) to send `EXPIRED` events. +Here's how you can do it: + +#### For a Standard Redis Instance + +1. **Edit Redis Configuration**: Open the `redis.conf` file and set the `notify-keyspace-events` parameter to enable expiry notifications. + + ```conf + notify-keyspace-events Ex + ``` + + This configuration enables notifications for key expirations (Ex), which is required for the listener to function correctly. + +2. **Using Redis CLI**: Alternatively, you can use the Redis CLI to set the configuration without editing the file directly. + + ```bash + redis-cli config set notify-keyspace-events Ex + ``` +This command will apply the changes immediately without needing a Redis restart. + +#### For AWS ElastiCache Redis +If you are using Redis on AWS ElastiCache, follow these steps to enable expiry notifications: + +1. **Access the AWS ElastiCache Console:** + + - Go to the ElastiCache dashboard on your AWS account. + +2. **Locate Your Redis Cluster:** + + - Find your cluster or replication group that you want to configure. + +3. **Modify the Parameter Group:** + + - Go to the **Parameter Groups** section. + - Find the parameter group associated with your cluster, or create a new one. + - Search for the `notify-keyspace-events` parameter and set its value to `Ex`. + - Save changes to the parameter group. + +4. **Attach the Parameter Group to Your Redis Cluster:** + + - Attach the modified parameter group to your Redis cluster. + - A **cluster reboot** may be required for the changes to take effect. + +After configuring the `notify-keyspace-events` parameter, Redis will publish `EXPIRED` events when keys expire, allowing the `laravel-super-cache` listener to process these events correctly. + + +### Scheduled Command for Orphaned Key Cleanup (Optionally but recommended) +Optionally but recommended, a scheduled command can be configured to periodically clean up any orphaned keys or sets left due to unexpected interruptions or errors. This adds an additional safety net to maintain consistency across cache keys and tags. + +```php +php artisan supercache:clean +``` + +This can be scheduled using Laravel's scheduler to run at appropriate intervals, ensuring your cache remains clean and optimized. + + +## Usage Examples +Below are examples of using the `SuperCacheManager` and its facade `SuperCache`: + +```php +use SuperCache\Facades\SuperCache; + +// Store an item in cache +SuperCache::put('user:1', $user); + +// Store an item in cache with tags +SuperCache::putWithTags('product:1', $product, ['products', 'featured']); + +// Retrieve an item from cache +$product = SuperCache::get('product:1'); + +// Check if a key exists +$exists = SuperCache::has('user:1'); + +// Increment a counter +SuperCache::increment('views:product:1'); + +// Decrement a counter +SuperCache::decrement('stock:product:1', 5); + +// Get all keys matching a pattern +$keys = SuperCache::getKeys(['product:*']); + +// Flush all cache +SuperCache::flush(); -```bash -php artisan migrate ``` +For all options see `SuperCacheManager` class. + + +## Architecture Overview + +![the-architecture.webp](resources%2Fimages%2Fthe-architecture.webp) + + +## Design Decisions and Performance + +### Key and Tag Organization +![add key.webp](resources%2Fimages%2Fadd%20key.webp) +![remove key.webp](resources%2Fimages%2Fremove%20key.webp) +Each cache key is stored with an associated set of tags. These tags allow efficient invalidation when certain categories of keys need to be cleared. The structure ensures that any cache invalidation affects only the necessary keys without touching unrelated data. +## Tag-Based Cache Architecture + +To efficiently handle cache keys and their associated tags, `laravel-super-cache` employs a well-defined architecture using Redis data structures. This ensures high performance for lookups, tag invalidations, and efficient management of keys. Below is a breakdown of how the package manages and stores these data structures in Redis. + +### 3 Main Structures in Redis + +1. **Key-Value Storage**: + - Each cache entry is stored as a key-value pair in Redis. + - **Naming Convention**: The cache key is prefixed for the package (e.g., `supercache:key:`), ensuring no conflicts with other Redis data. + +2. **Set for Each Tag (Tag-Key Sets)**: + - For every tag associated with a key, a Redis set is created to hold all the keys associated with that tag. + - **Naming Convention**: Each set is named with a pattern like `supercache:tag::shard:`, where `` is the tag name and `` is determined by the sharding algorithm. + - These sets allow quick retrieval of all keys associated with a tag, facilitating efficient cache invalidation by tag. + +3. **Set for Each Key (Key-Tag Sets)**: + - Each cache key has a set that holds all the tags associated with that key. + - **Naming Convention**: The set is named as `supercache:tags:`, where `` is the actual cache key. + - This structure allows quick identification of which tags are associated with a key, ensuring efficient clean-up when a key expires. + +### Sharding for Efficient Tag Management + +To optimize performance when dealing with potentially large sets of keys associated with a single tag, `laravel-super-cache` employs a **sharding strategy**: + +- **Why Sharding?**: A single tag might be associated with a large number of keys. If all keys for a tag were stored in a single set, this could degrade performance. Sharding splits these keys across multiple smaller sets, distributing the load. +- **How Sharding Works**: When a key is added to a tag, a fast hash function (e.g., `xxHash32`) is used to compute a shard index. The key is then stored in the appropriate shard for that tag. + - **Naming Convention for Sharded Sets**: Each set for a tag is named as `supercache:tag::shard:`. + - The number of shards is configurable through the `SUPERCACHE_NUM_SHARDS` setting, allowing you to balance between performance and memory usage. + +### Example: Creating a Cache Key with Tags + +When you create a cache key with associated tags, here's what happens: + +1. **Key-Value Pair**: A key-value pair is stored in Redis, prefixed as `supercache:key:`. +2. **Tag-Key Sets**: For each tag associated with the key: + - A shard is determined using a hash function. + - The key is added to the corresponding sharded set for the tag, named as `supercache:tag::shard:`. +3. **Key-Tag Set**: A set is created to associate the key with its tags, named as `supercache:tags:`. + +This structure allows efficient lookup, tagging, and invalidation of cache entries. + +#### Example +Suppose you cache a key `product:123` with tags `electronics` and `featured`. The following structures would be created in Redis: + +- **Key-Value Pair**: + - `supercache:key:product:123` -> `` + +- **Tag-Key Sets**: + - Assuming `SUPERCACHE_NUM_SHARDS` is set to 256, and `product:123` hashes to shard `42` for `electronics` and shard `85` for `featured`: + - `supercache:tag:electronics:shard:42` -> contains `supercache:key:product:123` + - `supercache:tag:featured:shard:85` -> contains `supercache:key:product:123` + +- **Key-Tag Set**: + - `supercache:tags:product:123` -> contains `electronics`, `featured` + +### Benefits of This Architecture + +- **Efficient Lookups and Invalidation**: Using sets for both tags and keys enables quick lookups and invalidation of cache entries when necessary. +- **Scalable Performance**: The sharding strategy distributes the keys associated with tags across multiple sets, ensuring performance remains high even when a tag has a large number of keys. +- **Atomic Operations**: When a cache key is added or removed, all necessary operations (like updating sets and shards) are executed atomically, ensuring data consistency. + +By following this architecture, `laravel-super-cache` is designed to handle high-volume cache operations efficiently while maintaining the flexibility and scalability needed for large enterprise applications. + + +### Sharding for Efficient Tag Management +![sharding.webp](resources%2Fimages%2Fsharding.webp) +Tags are distributed across multiple shards to optimize performance. +When a key is associated with a tag, it is added to a specific shard determined by a fast hashing function (xxHash32). +This sharding reduces the performance bottleneck by preventing single large sets from slowing down the cache operations. + +### Locks for Concurrency Control +![locks.webp](resources%2Fimages%2Flocks.webp) +When a key is processed for expiration, an optimistic lock is acquired to prevent race conditions, ensuring that no concurrent process attempts to alter the same key simultaneously. The lock has a short TTL to ensure it is quickly released after processing. + +### Namespace Suffix for Parallel Processing +To handle high volumes of expiry notifications efficiently, the listener processes them in parallel by suffixing namespaces to keys. Each process handles notifications for a specific namespace, allowing for scalable parallel processing. The listener uses batch processing with Lua scripting to optimize performance. + +### Redis Expiry Notifications and Listener System + +#### Handling Expire Correctly with TTLs +![expires.webp](resources%2Fimages%2Fexpires.webp) +To ensure keys are deleted from Redis when they expire, a combination of TTL and expiry notifications is used. If a key expires naturally, the listener is notified to clean up any associated tag sets. This ensures no "orphan" references are left in the cache. + +#### Listener for Expiry Notifications +![listener.webp](resources%2Fimages%2Flistener.webp) +The listener is responsible for handling expired cache keys and cleaning up associated tags efficently by using mixed technics LUA script + batch processing + pipeline. +You can run multiple processes in parallel by setting different {namespace} values. This allows the listener to handle notifications in parallel for optimal performance. + +To ensure that keys are cleaned up efficiently upon expiration, `laravel-super-cache` uses Redis expiry notifications in combination with a dedicated listener process. This section explains how the notification system works, how the listener consumes these events, and the benefits of using batching, pipelines, and Lua scripts for performance optimization. + +#### Redis Expiry Notifications: How They Work + +Redis has a mechanism to publish notifications when certain events occur. One of these events is the expiration of keys (`EXPIRED`). The `laravel-super-cache` package uses these expiry notifications to clean up the tag associations whenever a cache key expires. + +**Enabling Expiry Notifications in Redis**: +- Redis must be configured to send `EXPIRED` notifications. This is done by setting the `notify-keyspace-events` parameter to include `Ex` (for expired events). +- When a key in Redis reaches its TTL (Time-To-Live) and expires, an `EXPIRED` event is published to the Redis notification channel. + +#### The Listener: Consuming Expiry Events + +The `supercache:listener` command is a long-running process that listens for these `EXPIRED` events and performs clean-up tasks when they are detected. Specifically, it: +1. **Subscribes to Redis Notifications**: The listener subscribes to the `EXPIRED` events, filtered by a specific namespace to avoid processing unrelated keys. +2. **Accumulates Expired Keys in Batches**: When a key expires, the listener adds it to an in-memory batch. This allows for processing multiple keys at once rather than handling each key individually. +3. **Processes Batches Using Pipelines and Lua Scripts**: Once a batch reaches a size or time threshold, it is processed in bulk using Redis pipelines or Lua scripts for maximum efficiency. + +### Performance Benefits: Batching, Pipeline, and Lua Scripts + +1. **Batching in Memory**: + - **What is it?** Instead of processing each expired key as soon as it is detected, the listener accumulates keys in a batch. + - **Benefits**: Reduces the overhead of individual operations, as multiple keys are processed together, reducing the number of calls to Redis. + +2. **Using Redis Pipelines**: + - **What is it?** A pipeline in Redis allows multiple commands to be sent to the server in one go, reducing the number of network round-trips. + - **Benefits**: Processing a batch of keys in a single pipeline operation is much faster than processing each key individually, as it minimizes network latency. + +3. **Executing Batch Operations with Lua Scripts**: + - **What is it?** Lua scripts allow you to execute multiple Redis commands atomically, ensuring that all operations on a batch of keys are processed as a single unit within Redis. + - **Benefits**: + - **Atomicity**: Ensures that all related operations (e.g., removing a key and cleaning up its associated tags) happen together without interference from other processes. + - **Performance**: Running a Lua script directly on the Redis server is faster than issuing multiple commands from an external client, as it reduces the need for multiple network calls and leverages Redis’s internal processing speed. + +### Parallel Processing with Namespaces + +To improve scalability, the listener allows processing multiple namespaces in parallel. Each listener process is assigned to handle a specific namespace, ensuring that the processing load is distributed evenly across multiple processes. + +- **Benefit**: Parallel processing enables your system to handle high volumes of expired keys efficiently, without creating a performance bottleneck. + +#### Example Workflow: Handling Key Expiry + +1. **Key Expiration Event**: A cache key `product:123` reaches its TTL and expires. Redis publishes an `EXPIRED` event for this key. +2. **Listener Accumulates Key**: The listener process receives the event and adds `product:123` to an in-memory batch. +3. **Batch Processing Triggered**: Once the batch reaches a size threshold (e.g., 100 keys) or a time threshold (e.g., 1 second), the listener triggers the batch processing. +4. **Batch Processing with Lua Script**: + - A Lua script is executed on Redis to: + - Verify if the key is actually expired (prevent race conditions). + - Remove the key from all associated tag sets. + - Delete the key-tag association set. + - This entire process is handled atomically by the Lua script for consistency and performance. + +### Why This Approach Optimizes Performance +By combining batching, pipelining, and Lua scripts, the package ensures: + +- **Reduced Network Overhead**: Fewer round-trips between your application and Redis. +- **Atomic Operations**: Lua scripts guarantee that all necessary operations for a key's expiry are handled in a single atomic block. +- **Efficient Resource Utilization**: Memory batching allows for efficient use of system resources, processing large numbers of keys quickly. +- **Parallel Scalability**: By using multiple listeners across namespaces, your system can handle large volumes of expirations without creating performance bottlenecks. +This approach provides a robust, scalable, and highly performant cache management system for enterprise-grade Laravel applications. + +### Scheduled Command for Orphaned Key Cleanup +Optionally, a scheduled command can be configured to periodically clean up any orphaned keys or sets left due to unexpected interruptions or errors. +This adds an additional safety net to maintain consistency across cache keys and tags. + +```php +php artisan supercache:clean +``` + +This can be scheduled using Laravel's scheduler to run at appropriate intervals, ensuring your cache remains clean and optimized. + + +## Conclusion +With laravel-super-cache, your Laravel application can achieve enterprise-grade caching performance with robust tag management, efficient key invalidation, and seamless parallel processing. Enjoy a faster, more reliable cache that scales effortlessly with your application's needs. + + ## Change log Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. diff --git a/composer.json b/composer.json index 81f64ad..2a328ff 100644 --- a/composer.json +++ b/composer.json @@ -1,14 +1,14 @@ { - "name": "padosoft/laravel-super-cache-invalidate", - "description": "A powerful package that provides an efficient and scalable cache invalidation system for Laravel applications. It is designed to handle high-throughput cache invalidation scenarios, such as those found in e-commerce platforms, by implementing advanced techniques like event queuing, coalescing, debouncing, sharding, and partitioning.", + "name": "padosoft/laravel-super-cache", + "description": "A Laravel package for advanced caching with tags and namespaces in Redis.", "keywords": [ "padosoft", "cache", - "laravel-super-cache-invalidate", + "laravel-super-cache", "redis", "redis-cache" ], - "homepage": "https://github.com/padosoft/laravel-super-cache-invalidate", + "homepage": "https://github.com/padosoft/laravel-super-cache", "license": "MIT", "authors": [ { @@ -41,12 +41,12 @@ }, "autoload": { "psr-4": { - "Padosoft\\SuperCacheInvalidate\\": "src" + "Padosoft\\SuperCache\\": "src" } }, "autoload-dev": { "psr-4": { - "Padosoft\\SuperCacheInvalidate\\Test\\": "tests" + "Padosoft\\SuperCache\\Test\\": "tests" } }, "config": { diff --git a/composer.lock b/composer.lock index e07a2e5..8a56646 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bcf444092d42bcfd8c4d410292cafe78", + "content-hash": "6f7e0c973f4b59b2327e59f9ce0a1e67", "packages": [ { "name": "brick/math", @@ -380,16 +380,16 @@ }, { "name": "dragonmantank/cron-expression", - "version": "v3.4.0", + "version": "v3.3.3", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "8c784d071debd117328803d86b2097615b457500" + "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/8c784d071debd117328803d86b2097615b457500", - "reference": "8c784d071debd117328803d86b2097615b457500", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/adfb1f505deb6384dc8b39804c5065dd3c8c8c0a", + "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a", "shasum": "" }, "require": { @@ -402,14 +402,10 @@ "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", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, "autoload": { "psr-4": { "Cron\\": "src/Cron/" @@ -433,7 +429,7 @@ ], "support": { "issues": "https://github.com/dragonmantank/cron-expression/issues", - "source": "https://github.com/dragonmantank/cron-expression/tree/v3.4.0" + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.3" }, "funding": [ { @@ -441,7 +437,7 @@ "type": "github" } ], - "time": "2024-10-09T13:47:03+00:00" + "time": "2023-08-10T19:36:49+00:00" }, { "name": "egulias/email-validator", @@ -731,16 +727,16 @@ }, { "name": "laravel/framework", - "version": "v10.48.24", + "version": "v10.48.22", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "2add73f71b88fc45ee1d4f3421f22366247f6155" + "reference": "c4ea52bb044faef4a103d7dd81746c01b2ec860e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/2add73f71b88fc45ee1d4f3421f22366247f6155", - "reference": "2add73f71b88fc45ee1d4f3421f22366247f6155", + "url": "https://api.github.com/repos/laravel/framework/zipball/c4ea52bb044faef4a103d7dd81746c01b2ec860e", + "reference": "c4ea52bb044faef4a103d7dd81746c01b2ec860e", "shasum": "" }, "require": { @@ -934,7 +930,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2024-11-20T15:57:07+00:00" + "time": "2024-09-12T15:00:09+00:00" }, { "name": "laravel/prompts", @@ -996,16 +992,16 @@ }, { "name": "laravel/serializable-closure", - "version": "v1.3.7", + "version": "v1.3.5", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "4f48ade902b94323ca3be7646db16209ec76be3d" + "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/4f48ade902b94323ca3be7646db16209ec76be3d", - "reference": "4f48ade902b94323ca3be7646db16209ec76be3d", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c", + "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c", "shasum": "" }, "require": { @@ -1053,7 +1049,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2024-11-14T18:34:49+00:00" + "time": "2024-09-23T13:33:08+00:00" }, { "name": "league/commonmark", @@ -1245,16 +1241,16 @@ }, { "name": "league/flysystem", - "version": "3.29.1", + "version": "3.29.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319" + "reference": "0adc0d9a51852e170e0028a60bd271726626d3f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/edc1bb7c86fab0776c3287dbd19b5fa278347319", - "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/0adc0d9a51852e170e0028a60bd271726626d3f0", + "reference": "0adc0d9a51852e170e0028a60bd271726626d3f0", "shasum": "" }, "require": { @@ -1322,9 +1318,9 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.29.1" + "source": "https://github.com/thephpleague/flysystem/tree/3.29.0" }, - "time": "2024-10-08T08:58:34+00:00" + "time": "2024-09-29T11:59:11+00:00" }, { "name": "league/flysystem-local", @@ -1433,16 +1429,16 @@ }, { "name": "monolog/monolog", - "version": "3.8.0", + "version": "3.7.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "32e515fdc02cdafbe4593e30a9350d486b125b67" + "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/32e515fdc02cdafbe4593e30a9350d486b125b67", - "reference": "32e515fdc02cdafbe4593e30a9350d486b125b67", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f4393b648b78a5408747de94fca38beb5f7e9ef8", + "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8", "shasum": "" }, "require": { @@ -1462,14 +1458,12 @@ "guzzlehttp/psr7": "^2.2", "mongodb/mongodb": "^1.8", "php-amqplib/php-amqplib": "~2.4 || ^3", - "php-console/php-console": "^3.1.8", - "phpstan/phpstan": "^2", - "phpstan/phpstan-deprecation-rules": "^2", - "phpstan/phpstan-strict-rules": "^2", - "phpunit/phpunit": "^10.5.17 || ^11.0.7", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-strict-rules": "^1.4", + "phpunit/phpunit": "^10.5.17", "predis/predis": "^1.1 || ^2", - "rollbar/rollbar": "^4.0", - "ruflin/elastica": "^7 || ^8", + "ruflin/elastica": "^7", "symfony/mailer": "^5.4 || ^6", "symfony/mime": "^5.4 || ^6" }, @@ -1520,7 +1514,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.8.0" + "source": "https://github.com/Seldaek/monolog/tree/3.7.0" }, "funding": [ { @@ -1532,7 +1526,7 @@ "type": "tidelift" } ], - "time": "2024-11-12T13:57:08+00:00" + "time": "2024-06-28T09:40:51+00:00" }, { "name": "nesbot/carbon", @@ -1643,24 +1637,24 @@ }, { "name": "nette/schema", - "version": "v1.3.2", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "da801d52f0354f70a638673c4a0f04e16529431d" + "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/da801d52f0354f70a638673c4a0f04e16529431d", - "reference": "da801d52f0354f70a638673c4a0f04e16529431d", + "url": "https://api.github.com/repos/nette/schema/zipball/a6d3a6d1f545f01ef38e60f375d1cf1f4de98188", + "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188", "shasum": "" }, "require": { "nette/utils": "^4.0", - "php": "8.1 - 8.4" + "php": "8.1 - 8.3" }, "require-dev": { - "nette/tester": "^2.5.2", + "nette/tester": "^2.4", "phpstan/phpstan-nette": "^1.0", "tracy/tracy": "^2.8" }, @@ -1699,9 +1693,9 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.3.2" + "source": "https://github.com/nette/schema/tree/v1.3.0" }, - "time": "2024-10-06T23:10:23+00:00" + "time": "2023-12-11T11:54:22+00:00" }, { "name": "nette/utils", @@ -1791,32 +1785,33 @@ }, { "name": "nunomaduro/termwind", - "version": "v1.16.0", + "version": "v1.15.1", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "dcf1ec3dfa36137b7ce41d43866644a7ab8fc257" + "reference": "8ab0b32c8caa4a2e09700ea32925441385e4a5dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/dcf1ec3dfa36137b7ce41d43866644a7ab8fc257", - "reference": "dcf1ec3dfa36137b7ce41d43866644a7ab8fc257", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/8ab0b32c8caa4a2e09700ea32925441385e4a5dc", + "reference": "8ab0b32c8caa4a2e09700ea32925441385e4a5dc", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": "^8.1", - "symfony/console": "^6.4.12" + "php": "^8.0", + "symfony/console": "^5.3.0|^6.0.0" }, "require-dev": { - "illuminate/console": "^10.48.22", - "illuminate/support": "^10.48.22", - "laravel/pint": "^1.18.1", - "pestphp/pest": "^2", - "pestphp/pest-plugin-mock": "2.0.0", - "phpstan/phpstan": "^1.12.6", - "phpstan/phpstan-strict-rules": "^1.6.1", - "symfony/var-dumper": "^6.4.11", + "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", @@ -1856,7 +1851,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v1.16.0" + "source": "https://github.com/nunomaduro/termwind/tree/v1.15.1" }, "funding": [ { @@ -1872,7 +1867,7 @@ "type": "github" } ], - "time": "2024-10-15T15:27:12+00:00" + "time": "2023-02-08T01:06:31+00:00" }, { "name": "phpoption/phpoption", @@ -2445,16 +2440,16 @@ }, { "name": "symfony/console", - "version": "v6.4.15", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "f1fc6f47283e27336e7cebb9e8946c8de7bff9bd" + "reference": "72d080eb9edf80e36c19be61f72c98ed8273b765" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/f1fc6f47283e27336e7cebb9e8946c8de7bff9bd", - "reference": "f1fc6f47283e27336e7cebb9e8946c8de7bff9bd", + "url": "https://api.github.com/repos/symfony/console/zipball/72d080eb9edf80e36c19be61f72c98ed8273b765", + "reference": "72d080eb9edf80e36c19be61f72c98ed8273b765", "shasum": "" }, "require": { @@ -2519,7 +2514,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.15" + "source": "https://github.com/symfony/console/tree/v6.4.12" }, "funding": [ { @@ -2535,20 +2530,20 @@ "type": "tidelift" } ], - "time": "2024-11-06T14:19:14+00:00" + "time": "2024-09-20T08:15:52+00:00" }, { "name": "symfony/css-selector", - "version": "v7.1.6", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "4aa4f6b3d6749c14d3aa815eef8226632e7bbc66" + "reference": "1c7cee86c6f812896af54434f8ce29c8d94f9ff4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/4aa4f6b3d6749c14d3aa815eef8226632e7bbc66", - "reference": "4aa4f6b3d6749c14d3aa815eef8226632e7bbc66", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/1c7cee86c6f812896af54434f8ce29c8d94f9ff4", + "reference": "1c7cee86c6f812896af54434f8ce29c8d94f9ff4", "shasum": "" }, "require": { @@ -2584,7 +2579,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.1.6" + "source": "https://github.com/symfony/css-selector/tree/v7.1.1" }, "funding": [ { @@ -2600,7 +2595,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "symfony/deprecation-contracts", @@ -2671,16 +2666,16 @@ }, { "name": "symfony/error-handler", - "version": "v6.4.14", + "version": "v6.4.10", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "9e024324511eeb00983ee76b9aedc3e6ecd993d9" + "reference": "231f1b2ee80f72daa1972f7340297d67439224f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/9e024324511eeb00983ee76b9aedc3e6ecd993d9", - "reference": "9e024324511eeb00983ee76b9aedc3e6ecd993d9", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/231f1b2ee80f72daa1972f7340297d67439224f0", + "reference": "231f1b2ee80f72daa1972f7340297d67439224f0", "shasum": "" }, "require": { @@ -2726,7 +2721,7 @@ "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.4.14" + "source": "https://github.com/symfony/error-handler/tree/v6.4.10" }, "funding": [ { @@ -2742,20 +2737,20 @@ "type": "tidelift" } ], - "time": "2024-11-05T15:34:40+00:00" + "time": "2024-07-26T12:30:32+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.1.6", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "87254c78dd50721cfd015b62277a8281c5589702" + "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/87254c78dd50721cfd015b62277a8281c5589702", - "reference": "87254c78dd50721cfd015b62277a8281c5589702", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", + "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", "shasum": "" }, "require": { @@ -2806,7 +2801,7 @@ "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/v7.1.6" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.1.1" }, "funding": [ { @@ -2822,7 +2817,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -2902,16 +2897,16 @@ }, { "name": "symfony/finder", - "version": "v6.4.13", + "version": "v6.4.11", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "daea9eca0b08d0ed1dc9ab702a46128fd1be4958" + "reference": "d7eb6daf8cd7e9ac4976e9576b32042ef7253453" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/daea9eca0b08d0ed1dc9ab702a46128fd1be4958", - "reference": "daea9eca0b08d0ed1dc9ab702a46128fd1be4958", + "url": "https://api.github.com/repos/symfony/finder/zipball/d7eb6daf8cd7e9ac4976e9576b32042ef7253453", + "reference": "d7eb6daf8cd7e9ac4976e9576b32042ef7253453", "shasum": "" }, "require": { @@ -2946,7 +2941,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.4.13" + "source": "https://github.com/symfony/finder/tree/v6.4.11" }, "funding": [ { @@ -2962,20 +2957,20 @@ "type": "tidelift" } ], - "time": "2024-10-01T08:30:56+00:00" + "time": "2024-08-13T14:27:37+00:00" }, { "name": "symfony/http-foundation", - "version": "v6.4.15", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "9b3165eb2f04aeaa1a5a2cfef73e63fe3b22dff6" + "reference": "133ac043875f59c26c55e79cf074562127cce4d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/9b3165eb2f04aeaa1a5a2cfef73e63fe3b22dff6", - "reference": "9b3165eb2f04aeaa1a5a2cfef73e63fe3b22dff6", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/133ac043875f59c26c55e79cf074562127cce4d2", + "reference": "133ac043875f59c26c55e79cf074562127cce4d2", "shasum": "" }, "require": { @@ -2985,12 +2980,12 @@ "symfony/polyfill-php83": "^1.27" }, "conflict": { - "symfony/cache": "<6.4.12|>=7.0,<7.1.5" + "symfony/cache": "<6.3" }, "require-dev": { "doctrine/dbal": "^2.13.1|^3|^4", "predis/predis": "^1.1|^2.0", - "symfony/cache": "^6.4.12|^7.1.5", + "symfony/cache": "^6.3|^7.0", "symfony/dependency-injection": "^5.4|^6.0|^7.0", "symfony/expression-language": "^5.4|^6.0|^7.0", "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4|^7.0", @@ -3023,7 +3018,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.4.15" + "source": "https://github.com/symfony/http-foundation/tree/v6.4.12" }, "funding": [ { @@ -3039,20 +3034,20 @@ "type": "tidelift" } ], - "time": "2024-11-08T16:09:24+00:00" + "time": "2024-09-20T08:18:25+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.4.15", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "b002a5b3947653c5aee3adac2a024ea615fd3ff5" + "reference": "96df83d51b5f78804f70c093b97310794fd6257b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b002a5b3947653c5aee3adac2a024ea615fd3ff5", - "reference": "b002a5b3947653c5aee3adac2a024ea615fd3ff5", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/96df83d51b5f78804f70c093b97310794fd6257b", + "reference": "96df83d51b5f78804f70c093b97310794fd6257b", "shasum": "" }, "require": { @@ -3137,7 +3132,7 @@ "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.4.15" + "source": "https://github.com/symfony/http-kernel/tree/v6.4.12" }, "funding": [ { @@ -3153,20 +3148,20 @@ "type": "tidelift" } ], - "time": "2024-11-13T13:57:37+00:00" + "time": "2024-09-21T06:02:57+00:00" }, { "name": "symfony/mailer", - "version": "v6.4.13", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "c2f7e0d8d7ac8fe25faccf5d8cac462805db2663" + "reference": "b6a25408c569ae2366b3f663a4edad19420a9c26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/c2f7e0d8d7ac8fe25faccf5d8cac462805db2663", - "reference": "c2f7e0d8d7ac8fe25faccf5d8cac462805db2663", + "url": "https://api.github.com/repos/symfony/mailer/zipball/b6a25408c569ae2366b3f663a4edad19420a9c26", + "reference": "b6a25408c569ae2366b3f663a4edad19420a9c26", "shasum": "" }, "require": { @@ -3217,7 +3212,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v6.4.13" + "source": "https://github.com/symfony/mailer/tree/v6.4.12" }, "funding": [ { @@ -3233,20 +3228,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2024-09-08T12:30:05+00:00" }, { "name": "symfony/mime", - "version": "v6.4.13", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "1de1cf14d99b12c7ebbb850491ec6ae3ed468855" + "reference": "abe16ee7790b16aa525877419deb0f113953f0e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/1de1cf14d99b12c7ebbb850491ec6ae3ed468855", - "reference": "1de1cf14d99b12c7ebbb850491ec6ae3ed468855", + "url": "https://api.github.com/repos/symfony/mime/zipball/abe16ee7790b16aa525877419deb0f113953f0e1", + "reference": "abe16ee7790b16aa525877419deb0f113953f0e1", "shasum": "" }, "require": { @@ -3302,7 +3297,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.4.13" + "source": "https://github.com/symfony/mime/tree/v6.4.12" }, "funding": [ { @@ -3318,7 +3313,7 @@ "type": "tidelift" } ], - "time": "2024-10-25T15:07:50+00:00" + "time": "2024-09-20T08:18:25+00:00" }, { "name": "symfony/polyfill-ctype", @@ -3958,16 +3953,16 @@ }, { "name": "symfony/process", - "version": "v6.4.15", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "3cb242f059c14ae08591c5c4087d1fe443564392" + "reference": "3f94e5f13ff58df371a7ead461b6e8068900fbb3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/3cb242f059c14ae08591c5c4087d1fe443564392", - "reference": "3cb242f059c14ae08591c5c4087d1fe443564392", + "url": "https://api.github.com/repos/symfony/process/zipball/3f94e5f13ff58df371a7ead461b6e8068900fbb3", + "reference": "3f94e5f13ff58df371a7ead461b6e8068900fbb3", "shasum": "" }, "require": { @@ -3999,7 +3994,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.4.15" + "source": "https://github.com/symfony/process/tree/v6.4.12" }, "funding": [ { @@ -4015,20 +4010,20 @@ "type": "tidelift" } ], - "time": "2024-11-06T14:19:14+00:00" + "time": "2024-09-17T12:47:12+00:00" }, { "name": "symfony/routing", - "version": "v6.4.13", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "640a74250d13f9c30d5ca045b6aaaabcc8215278" + "reference": "a7c8036bd159486228dc9be3e846a00a0dda9f9f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/640a74250d13f9c30d5ca045b6aaaabcc8215278", - "reference": "640a74250d13f9c30d5ca045b6aaaabcc8215278", + "url": "https://api.github.com/repos/symfony/routing/zipball/a7c8036bd159486228dc9be3e846a00a0dda9f9f", + "reference": "a7c8036bd159486228dc9be3e846a00a0dda9f9f", "shasum": "" }, "require": { @@ -4082,7 +4077,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.4.13" + "source": "https://github.com/symfony/routing/tree/v6.4.12" }, "funding": [ { @@ -4098,7 +4093,7 @@ "type": "tidelift" } ], - "time": "2024-10-01T08:30:56+00:00" + "time": "2024-09-20T08:32:26+00:00" }, { "name": "symfony/service-contracts", @@ -4185,16 +4180,16 @@ }, { "name": "symfony/string", - "version": "v7.1.8", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "591ebd41565f356fcd8b090fe64dbb5878f50281" + "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/591ebd41565f356fcd8b090fe64dbb5878f50281", - "reference": "591ebd41565f356fcd8b090fe64dbb5878f50281", + "url": "https://api.github.com/repos/symfony/string/zipball/d66f9c343fa894ec2037cc928381df90a7ad4306", + "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306", "shasum": "" }, "require": { @@ -4252,7 +4247,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.1.8" + "source": "https://github.com/symfony/string/tree/v7.1.5" }, "funding": [ { @@ -4268,20 +4263,20 @@ "type": "tidelift" } ], - "time": "2024-11-13T13:31:21+00:00" + "time": "2024-09-20T08:28:38+00:00" }, { "name": "symfony/translation", - "version": "v6.4.13", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "bee9bfabfa8b4045a66bf82520e492cddbaffa66" + "reference": "cf8360b8352b086be620fae8342c4d96e391a489" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/bee9bfabfa8b4045a66bf82520e492cddbaffa66", - "reference": "bee9bfabfa8b4045a66bf82520e492cddbaffa66", + "url": "https://api.github.com/repos/symfony/translation/zipball/cf8360b8352b086be620fae8342c4d96e391a489", + "reference": "cf8360b8352b086be620fae8342c4d96e391a489", "shasum": "" }, "require": { @@ -4347,7 +4342,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.4.13" + "source": "https://github.com/symfony/translation/tree/v6.4.12" }, "funding": [ { @@ -4363,7 +4358,7 @@ "type": "tidelift" } ], - "time": "2024-09-27T18:14:25+00:00" + "time": "2024-09-16T06:02:54+00:00" }, { "name": "symfony/translation-contracts", @@ -4445,16 +4440,16 @@ }, { "name": "symfony/uid", - "version": "v6.4.13", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "18eb207f0436a993fffbdd811b5b8fa35fa5e007" + "reference": "2f16054e0a9b194b8ca581d4a64eee3f7d4a9d4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/18eb207f0436a993fffbdd811b5b8fa35fa5e007", - "reference": "18eb207f0436a993fffbdd811b5b8fa35fa5e007", + "url": "https://api.github.com/repos/symfony/uid/zipball/2f16054e0a9b194b8ca581d4a64eee3f7d4a9d4d", + "reference": "2f16054e0a9b194b8ca581d4a64eee3f7d4a9d4d", "shasum": "" }, "require": { @@ -4499,7 +4494,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v6.4.13" + "source": "https://github.com/symfony/uid/tree/v6.4.12" }, "funding": [ { @@ -4515,20 +4510,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2024-09-20T08:32:26+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.4.15", + "version": "v6.4.11", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "38254d5a5ac2e61f2b52f9caf54e7aa3c9d36b80" + "reference": "ee14c8254a480913268b1e3b1cba8045ed122694" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38254d5a5ac2e61f2b52f9caf54e7aa3c9d36b80", - "reference": "38254d5a5ac2e61f2b52f9caf54e7aa3c9d36b80", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/ee14c8254a480913268b1e3b1cba8045ed122694", + "reference": "ee14c8254a480913268b1e3b1cba8045ed122694", "shasum": "" }, "require": { @@ -4584,7 +4579,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.4.15" + "source": "https://github.com/symfony/var-dumper/tree/v6.4.11" }, "funding": [ { @@ -4600,7 +4595,7 @@ "type": "tidelift" } ], - "time": "2024-11-08T15:28:48+00:00" + "time": "2024-08-30T16:03:21+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -4741,16 +4736,16 @@ }, { "name": "voku/portable-ascii", - "version": "2.0.3", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/voku/portable-ascii.git", - "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d" + "reference": "b56450eed252f6801410d810c8e1727224ae0743" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", - "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b56450eed252f6801410d810c8e1727224ae0743", + "reference": "b56450eed252f6801410d810c8e1727224ae0743", "shasum": "" }, "require": { @@ -4775,7 +4770,7 @@ "authors": [ { "name": "Lars Moelleken", - "homepage": "https://www.moelleken.org/" + "homepage": "http://www.moelleken.org/" } ], "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", @@ -4787,7 +4782,7 @@ ], "support": { "issues": "https://github.com/voku/portable-ascii/issues", - "source": "https://github.com/voku/portable-ascii/tree/2.0.3" + "source": "https://github.com/voku/portable-ascii/tree/2.0.1" }, "funding": [ { @@ -4811,7 +4806,7 @@ "type": "tidelift" } ], - "time": "2024-11-21T01:49:47+00:00" + "time": "2022-03-08T17:03:00+00:00" }, { "name": "webmozart/assert", @@ -4939,16 +4934,16 @@ }, { "name": "composer/pcre", - "version": "3.3.2", + "version": "3.3.1", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + "reference": "63aaeac21d7e775ff9bc9d45021e1745c97521c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "url": "https://api.github.com/repos/composer/pcre/zipball/63aaeac21d7e775ff9bc9d45021e1745c97521c4", + "reference": "63aaeac21d7e775ff9bc9d45021e1745c97521c4", "shasum": "" }, "require": { @@ -4958,8 +4953,8 @@ "phpstan/phpstan": "<1.11.10" }, "require-dev": { - "phpstan/phpstan": "^1.12 || ^2", - "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpstan/phpstan": "^1.11.10", + "phpstan/phpstan-strict-rules": "^1.1", "phpunit/phpunit": "^8 || ^9" }, "type": "library", @@ -4998,7 +4993,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.3.2" + "source": "https://github.com/composer/pcre/tree/3.3.1" }, "funding": [ { @@ -5014,7 +5009,7 @@ "type": "tidelift" } ], - "time": "2024-11-12T16:29:46+00:00" + "time": "2024-08-27T18:44:43+00:00" }, { "name": "composer/semver", @@ -5212,16 +5207,16 @@ }, { "name": "fakerphp/faker", - "version": "v1.24.0", + "version": "v1.23.1", "source": { "type": "git", "url": "https://github.com/FakerPHP/Faker.git", - "reference": "a136842a532bac9ecd8a1c723852b09915d7db50" + "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/a136842a532bac9ecd8a1c723852b09915d7db50", - "reference": "a136842a532bac9ecd8a1c723852b09915d7db50", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/bfb4fe148adbf78eff521199619b93a52ae3554b", + "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b", "shasum": "" }, "require": { @@ -5269,9 +5264,9 @@ ], "support": { "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v1.24.0" + "source": "https://github.com/FakerPHP/Faker/tree/v1.23.1" }, - "time": "2024-11-07T15:11:20+00:00" + "time": "2024-01-02T13:46:09+00:00" }, { "name": "fidry/cpu-core-counter", @@ -5677,39 +5672,36 @@ }, { "name": "larastan/larastan", - "version": "v2.9.11", + "version": "v2.9.8", "source": { "type": "git", "url": "https://github.com/larastan/larastan.git", - "reference": "54eccd35d1732b9ee4392c25aec606a6a9c521e7" + "reference": "340badd89b0eb5bddbc503a4829c08cf9a2819d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/larastan/larastan/zipball/54eccd35d1732b9ee4392c25aec606a6a9c521e7", - "reference": "54eccd35d1732b9ee4392c25aec606a6a9c521e7", + "url": "https://api.github.com/repos/larastan/larastan/zipball/340badd89b0eb5bddbc503a4829c08cf9a2819d7", + "reference": "340badd89b0eb5bddbc503a4829c08cf9a2819d7", "shasum": "" }, "require": { "ext-json": "*", - "illuminate/console": "^9.52.16 || ^10.28.0 || ^11.16", - "illuminate/container": "^9.52.16 || ^10.28.0 || ^11.16", - "illuminate/contracts": "^9.52.16 || ^10.28.0 || ^11.16", - "illuminate/database": "^9.52.16 || ^10.28.0 || ^11.16", - "illuminate/http": "^9.52.16 || ^10.28.0 || ^11.16", - "illuminate/pipeline": "^9.52.16 || ^10.28.0 || ^11.16", - "illuminate/support": "^9.52.16 || ^10.28.0 || ^11.16", + "illuminate/console": "^9.52.16 || ^10.28.0 || ^11.0", + "illuminate/container": "^9.52.16 || ^10.28.0 || ^11.0", + "illuminate/contracts": "^9.52.16 || ^10.28.0 || ^11.0", + "illuminate/database": "^9.52.16 || ^10.28.0 || ^11.0", + "illuminate/http": "^9.52.16 || ^10.28.0 || ^11.0", + "illuminate/pipeline": "^9.52.16 || ^10.28.0 || ^11.0", + "illuminate/support": "^9.52.16 || ^10.28.0 || ^11.0", "php": "^8.0.2", "phpmyadmin/sql-parser": "^5.9.0", - "phpstan/phpstan": "^1.12.5" + "phpstan/phpstan": "^1.11.2" }, "require-dev": { "doctrine/coding-standard": "^12.0", - "laravel/framework": "^9.52.16 || ^10.28.0 || ^11.16", - "mockery/mockery": "^1.5.1", "nikic/php-parser": "^4.19.1", "orchestra/canvas": "^7.11.1 || ^8.11.0 || ^9.0.2", - "orchestra/testbench-core": "^7.33.0 || ^8.13.0 || ^9.0.9", - "phpstan/phpstan-deprecation-rules": "^1.2", + "orchestra/testbench": "^7.33.0 || ^8.13.0 || ^9.0.3", "phpunit/phpunit": "^9.6.13 || ^10.5.16" }, "suggest": { @@ -5758,7 +5750,7 @@ ], "support": { "issues": "https://github.com/larastan/larastan/issues", - "source": "https://github.com/larastan/larastan/tree/v2.9.11" + "source": "https://github.com/larastan/larastan/tree/v2.9.8" }, "funding": [ { @@ -5778,20 +5770,20 @@ "type": "patreon" } ], - "time": "2024-11-11T23:11:00+00:00" + "time": "2024-07-06T17:46:02+00:00" }, { "name": "laravel/pint", - "version": "v1.18.2", + "version": "v1.18.1", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "f55daaf7eb6c2f49ddf6702fb42e3091c64d8a64" + "reference": "35c00c05ec43e6b46d295efc0f4386ceb30d50d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/f55daaf7eb6c2f49ddf6702fb42e3091c64d8a64", - "reference": "f55daaf7eb6c2f49ddf6702fb42e3091c64d8a64", + "url": "https://api.github.com/repos/laravel/pint/zipball/35c00c05ec43e6b46d295efc0f4386ceb30d50d9", + "reference": "35c00c05ec43e6b46d295efc0f4386ceb30d50d9", "shasum": "" }, "require": { @@ -5844,7 +5836,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2024-11-20T09:33:46+00:00" + "time": "2024-09-24T17:22:50+00:00" }, { "name": "laravel/tinker", @@ -5997,16 +5989,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.12.1", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", "shasum": "" }, "require": { @@ -6045,7 +6037,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" }, "funding": [ { @@ -6053,20 +6045,20 @@ "type": "tidelift" } ], - "time": "2024-11-08T17:47:46+00:00" + "time": "2024-06-12T14:39:25+00:00" }, { "name": "nikic/php-parser", - "version": "v5.3.1", + "version": "v5.3.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" + "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3abf7425cd284141dc5d8d14a9ee444de3345d1a", + "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a", "shasum": "" }, "require": { @@ -6109,46 +6101,46 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.0" }, - "time": "2024-10-08T18:51:32+00:00" + "time": "2024-09-29T13:56:26+00:00" }, { "name": "nunomaduro/collision", - "version": "v7.11.0", + "version": "v7.10.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "994ea93df5d4132f69d3f1bd74730509df6e8a05" + "reference": "49ec67fa7b002712da8526678abd651c09f375b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/994ea93df5d4132f69d3f1bd74730509df6e8a05", - "reference": "994ea93df5d4132f69d3f1bd74730509df6e8a05", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/49ec67fa7b002712da8526678abd651c09f375b2", + "reference": "49ec67fa7b002712da8526678abd651c09f375b2", "shasum": "" }, "require": { - "filp/whoops": "^2.16.0", + "filp/whoops": "^2.15.3", "nunomaduro/termwind": "^1.15.1", "php": "^8.1.0", - "symfony/console": "^6.4.12" + "symfony/console": "^6.3.4" }, "conflict": { "laravel/framework": ">=11.0.0" }, "require-dev": { - "brianium/paratest": "^7.3.1", - "laravel/framework": "^10.48.22", - "laravel/pint": "^1.18.1", - "laravel/sail": "^1.36.0", - "laravel/sanctum": "^3.3.3", - "laravel/tinker": "^2.10.0", - "nunomaduro/larastan": "^2.9.8", - "orchestra/testbench-core": "^8.28.3", - "pestphp/pest": "^2.35.1", - "phpunit/phpunit": "^10.5.36", - "sebastian/environment": "^6.1.0", - "spatie/laravel-ignition": "^2.8.0" + "brianium/paratest": "^7.3.0", + "laravel/framework": "^10.28.0", + "laravel/pint": "^1.13.3", + "laravel/sail": "^1.25.0", + "laravel/sanctum": "^3.3.1", + "laravel/tinker": "^2.8.2", + "nunomaduro/larastan": "^2.6.4", + "orchestra/testbench-core": "^8.13.0", + "pestphp/pest": "^2.23.2", + "phpunit/phpunit": "^10.4.1", + "sebastian/environment": "^6.0.1", + "spatie/laravel-ignition": "^2.3.1" }, "type": "library", "extra": { @@ -6207,7 +6199,7 @@ "type": "patreon" } ], - "time": "2024-10-15T15:12:40+00:00" + "time": "2023-10-11T15:45:01+00:00" }, { "name": "orchestra/canvas", @@ -6358,25 +6350,25 @@ }, { "name": "orchestra/testbench", - "version": "v8.28.0", + "version": "v8.27.0", "source": { "type": "git", "url": "https://github.com/orchestral/testbench.git", - "reference": "96beb6646dc2b766b92ba40379a56999a554904a" + "reference": "8abdce816a467f450ca88a4a451d11eb4f056ed2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/orchestral/testbench/zipball/96beb6646dc2b766b92ba40379a56999a554904a", - "reference": "96beb6646dc2b766b92ba40379a56999a554904a", + "url": "https://api.github.com/repos/orchestral/testbench/zipball/8abdce816a467f450ca88a4a451d11eb4f056ed2", + "reference": "8abdce816a467f450ca88a4a451d11eb4f056ed2", "shasum": "" }, "require": { "composer-runtime-api": "^2.2", "fakerphp/faker": "^1.21", - "laravel/framework": "^10.48.23", + "laravel/framework": "^10.48.22", "mockery/mockery": "^1.5.1", - "orchestra/testbench-core": "^8.29", - "orchestra/workbench": "^8.12", + "orchestra/testbench-core": "^8.28.1", + "orchestra/workbench": "^8.10", "php": "^8.1", "phpunit/phpunit": "^9.6 || ^10.1", "symfony/process": "^6.2", @@ -6407,33 +6399,33 @@ ], "support": { "issues": "https://github.com/orchestral/testbench/issues", - "source": "https://github.com/orchestral/testbench/tree/v8.28.0" + "source": "https://github.com/orchestral/testbench/tree/v8.27.0" }, - "time": "2024-11-18T23:55:06+00:00" + "time": "2024-09-24T05:22:46+00:00" }, { "name": "orchestra/testbench-core", - "version": "v8.29.0", + "version": "v8.28.1", "source": { "type": "git", "url": "https://github.com/orchestral/testbench-core.git", - "reference": "55cf0234f9f96590bca4ece7081cc5c328e34e48" + "reference": "a5ab70e78378363b69220ae5771dabe8c885a25e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/orchestral/testbench-core/zipball/55cf0234f9f96590bca4ece7081cc5c328e34e48", - "reference": "55cf0234f9f96590bca4ece7081cc5c328e34e48", + "url": "https://api.github.com/repos/orchestral/testbench-core/zipball/a5ab70e78378363b69220ae5771dabe8c885a25e", + "reference": "a5ab70e78378363b69220ae5771dabe8c885a25e", "shasum": "" }, "require": { "composer-runtime-api": "^2.2", "php": "^8.1", - "symfony/polyfill-php83": "^1.31" + "symfony/polyfill-php83": "^1.28" }, "conflict": { "brianium/paratest": "<6.4.0 || >=7.0.0 <7.1.4 || >=8.0.0", - "laravel/framework": "<10.48.23 || >=11.0.0", - "laravel/serializable-closure": "<1.3.0 || >=3.0.0", + "laravel/framework": "<10.48.22 || >=11.0.0", + "laravel/serializable-closure": "<1.3.0 || >=2.0.0", "nunomaduro/collision": "<6.4.0 || >=7.0.0 <7.4.0 || >=8.0.0", "orchestra/testbench-dusk": "<8.21.0 || >=9.0.0", "orchestra/workbench": "<1.0.0", @@ -6441,11 +6433,10 @@ }, "require-dev": { "fakerphp/faker": "^1.21", - "laravel/framework": "^10.48.23", + "laravel/framework": "^10.48.22", "laravel/pint": "^1.17", - "laravel/serializable-closure": "^1.3 || ^2.0", "mockery/mockery": "^1.5.1", - "phpstan/phpstan": "^2.0", + "phpstan/phpstan": "^1.11", "phpunit/phpunit": "^10.1", "spatie/laravel-ray": "^1.32.4", "symfony/process": "^6.2", @@ -6456,7 +6447,7 @@ "brianium/paratest": "Allow using parallel testing (^6.4 || ^7.1.4).", "ext-pcntl": "Required to use all features of the console signal trapping.", "fakerphp/faker": "Allow using Faker for testing (^1.21).", - "laravel/framework": "Required for testing (^10.48.23).", + "laravel/framework": "Required for testing (^10.48.20).", "mockery/mockery": "Allow using Mockery for testing (^1.5.1).", "nunomaduro/collision": "Allow using Laravel style tests output and parallel testing (^6.4 || ^7.4).", "orchestra/testbench-browser-kit": "Allow using legacy Laravel BrowserKit for testing (^8.0).", @@ -6503,38 +6494,39 @@ "issues": "https://github.com/orchestral/testbench/issues", "source": "https://github.com/orchestral/testbench-core" }, - "time": "2024-11-18T12:42:00+00:00" + "time": "2024-09-24T05:26:51+00:00" }, { "name": "orchestra/workbench", - "version": "v8.12.0", + "version": "v8.10.0", "source": { "type": "git", "url": "https://github.com/orchestral/workbench.git", - "reference": "68a0042861ea4f9ace68d74a49e70aa5031244e7" + "reference": "17329eae5f82c9fe6daa7e468956b618ca44cc42" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/orchestral/workbench/zipball/68a0042861ea4f9ace68d74a49e70aa5031244e7", - "reference": "68a0042861ea4f9ace68d74a49e70aa5031244e7", + "url": "https://api.github.com/repos/orchestral/workbench/zipball/17329eae5f82c9fe6daa7e468956b618ca44cc42", + "reference": "17329eae5f82c9fe6daa7e468956b618ca44cc42", "shasum": "" }, "require": { "composer-runtime-api": "^2.2", "fakerphp/faker": "^1.21", - "laravel/framework": "^10.48.23", + "laravel/framework": "^10.48.2", "laravel/tinker": "^2.8.2", "nunomaduro/collision": "^6.4 || ^7.10", "orchestra/canvas": "^8.11.9", - "orchestra/testbench-core": "^8.29", + "orchestra/testbench-core": "^8.27", "php": "^8.1", + "spatie/laravel-ray": "^1.32.4", "symfony/polyfill-php83": "^1.28", "symfony/yaml": "^6.2" }, "require-dev": { "laravel/pint": "^1.17", "mockery/mockery": "^1.5.1", - "phpstan/phpstan": "^2.0", + "phpstan/phpstan": "^1.11", "phpunit/phpunit": "^10.1", "symfony/process": "^6.2" }, @@ -6542,6 +6534,11 @@ "ext-pcntl": "Required to use all features of the console signal trapping." }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.5.x-dev" + } + }, "autoload": { "psr-4": { "Orchestra\\Workbench\\": "src/" @@ -6566,9 +6563,9 @@ ], "support": { "issues": "https://github.com/orchestral/workbench/issues", - "source": "https://github.com/orchestral/workbench/tree/v8.12.0" + "source": "https://github.com/orchestral/workbench/tree/v8.10.0" }, - "time": "2024-11-18T23:06:06+00:00" + "time": "2024-08-26T05:33:39+00:00" }, { "name": "pdepend/pdepend", @@ -7026,16 +7023,16 @@ }, { "name": "phpmyadmin/sql-parser", - "version": "5.10.1", + "version": "5.10.0", "source": { "type": "git", "url": "https://github.com/phpmyadmin/sql-parser.git", - "reference": "b14fd66496a22d8dd7f7e2791edd9e8674422f17" + "reference": "91d980ab76c3f152481e367f62b921adc38af451" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpmyadmin/sql-parser/zipball/b14fd66496a22d8dd7f7e2791edd9e8674422f17", - "reference": "b14fd66496a22d8dd7f7e2791edd9e8674422f17", + "url": "https://api.github.com/repos/phpmyadmin/sql-parser/zipball/91d980ab76c3f152481e367f62b921adc38af451", + "reference": "91d980ab76c3f152481e367f62b921adc38af451", "shasum": "" }, "require": { @@ -7109,20 +7106,20 @@ "type": "other" } ], - "time": "2024-11-10T04:10:31+00:00" + "time": "2024-08-29T20:56:34+00:00" }, { "name": "phpstan/phpstan", - "version": "1.12.11", + "version": "1.12.5", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "0d1fc20a962a91be578bcfe7cf939e6e1a2ff733" + "reference": "7e6c6cb7cecb0a6254009a1a8a7d54ec99812b17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0d1fc20a962a91be578bcfe7cf939e6e1a2ff733", - "reference": "0d1fc20a962a91be578bcfe7cf939e6e1a2ff733", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/7e6c6cb7cecb0a6254009a1a8a7d54ec99812b17", + "reference": "7e6c6cb7cecb0a6254009a1a8a7d54ec99812b17", "shasum": "" }, "require": { @@ -7167,7 +7164,7 @@ "type": "github" } ], - "time": "2024-11-17T14:08:01+00:00" + "time": "2024-09-26T12:45:22+00:00" }, { "name": "phpunit/php-code-coverage", @@ -7492,16 +7489,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.38", + "version": "10.5.35", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132" + "reference": "7ac8b4e63f456046dcb4c9787da9382831a1874b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a86773b9e887a67bc53efa9da9ad6e3f2498c132", - "reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/7ac8b4e63f456046dcb4c9787da9382831a1874b", + "reference": "7ac8b4e63f456046dcb4c9787da9382831a1874b", "shasum": "" }, "require": { @@ -7522,7 +7519,7 @@ "phpunit/php-timer": "^6.0.0", "sebastian/cli-parser": "^2.0.1", "sebastian/code-unit": "^2.0.0", - "sebastian/comparator": "^5.0.3", + "sebastian/comparator": "^5.0.2", "sebastian/diff": "^5.1.1", "sebastian/environment": "^6.1.0", "sebastian/exporter": "^5.1.2", @@ -7573,7 +7570,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.38" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.35" }, "funding": [ { @@ -7589,7 +7586,7 @@ "type": "tidelift" } ], - "time": "2024-10-28T13:06:21+00:00" + "time": "2024-09-19T10:52:21+00:00" }, { "name": "psr/http-factory", @@ -8354,16 +8351,16 @@ }, { "name": "rector/rector", - "version": "1.2.10", + "version": "1.2.6", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "40f9cf38c05296bd32f444121336a521a293fa61" + "reference": "6ca85da28159dbd3bb36211c5104b7bc91278e99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/40f9cf38c05296bd32f444121336a521a293fa61", - "reference": "40f9cf38c05296bd32f444121336a521a293fa61", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/6ca85da28159dbd3bb36211c5104b7bc91278e99", + "reference": "6ca85da28159dbd3bb36211c5104b7bc91278e99", "shasum": "" }, "require": { @@ -8401,7 +8398,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/1.2.10" + "source": "https://github.com/rectorphp/rector/tree/1.2.6" }, "funding": [ { @@ -8409,7 +8406,7 @@ "type": "github" } ], - "time": "2024-11-08T13:59:10+00:00" + "time": "2024-10-03T08:56:44+00:00" }, { "name": "sebastian/cli-parser", @@ -8581,16 +8578,16 @@ }, { "name": "sebastian/comparator", - "version": "5.0.3", + "version": "5.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e" + "reference": "2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", - "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53", + "reference": "2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53", "shasum": "" }, "require": { @@ -8601,7 +8598,7 @@ "sebastian/exporter": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^10.5" + "phpunit/phpunit": "^10.4" }, "type": "library", "extra": { @@ -8646,7 +8643,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.3" + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.2" }, "funding": [ { @@ -8654,7 +8651,7 @@ "type": "github" } ], - "time": "2024-10-18T14:56:07+00:00" + "time": "2024-08-12T06:03:08+00:00" }, { "name": "sebastian/complexity", @@ -9329,16 +9326,16 @@ }, { "name": "spatie/backtrace", - "version": "1.6.3", + "version": "1.6.2", "source": { "type": "git", "url": "https://github.com/spatie/backtrace.git", - "reference": "7c18db2bc667ac84e5d7c18e33f16c38ff2d8838" + "reference": "1a9a145b044677ae3424693f7b06479fc8c137a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/backtrace/zipball/7c18db2bc667ac84e5d7c18e33f16c38ff2d8838", - "reference": "7c18db2bc667ac84e5d7c18e33f16c38ff2d8838", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/1a9a145b044677ae3424693f7b06479fc8c137a9", + "reference": "1a9a145b044677ae3424693f7b06479fc8c137a9", "shasum": "" }, "require": { @@ -9376,7 +9373,7 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/backtrace/tree/1.6.3" + "source": "https://github.com/spatie/backtrace/tree/1.6.2" }, "funding": [ { @@ -9388,7 +9385,7 @@ "type": "other" } ], - "time": "2024-11-18T14:58:58+00:00" + "time": "2024-07-22T08:21:24+00:00" }, { "name": "spatie/error-solutions", @@ -9931,16 +9928,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.11.1", + "version": "3.10.3", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "19473c30efe4f7b3cd42522d0b2e6e7f243c6f87" + "reference": "62d32998e820bddc40f99f8251958aed187a5c9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/19473c30efe4f7b3cd42522d0b2e6e7f243c6f87", - "reference": "19473c30efe4f7b3cd42522d0b2e6e7f243c6f87", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/62d32998e820bddc40f99f8251958aed187a5c9c", + "reference": "62d32998e820bddc40f99f8251958aed187a5c9c", "shasum": "" }, "require": { @@ -10007,20 +10004,20 @@ "type": "open_collective" } ], - "time": "2024-11-16T12:02:36+00:00" + "time": "2024-09-18T10:38:58+00:00" }, { "name": "symfony/config", - "version": "v7.1.7", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "dc373a5cbd345354696f5dfd39c5c7a8ea23f4c8" + "reference": "2210fc99fa42a259eb6c89d1f724ce0c4d62d5d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/dc373a5cbd345354696f5dfd39c5c7a8ea23f4c8", - "reference": "dc373a5cbd345354696f5dfd39c5c7a8ea23f4c8", + "url": "https://api.github.com/repos/symfony/config/zipball/2210fc99fa42a259eb6c89d1f724ce0c4d62d5d2", + "reference": "2210fc99fa42a259eb6c89d1f724ce0c4d62d5d2", "shasum": "" }, "require": { @@ -10066,7 +10063,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v7.1.7" + "source": "https://github.com/symfony/config/tree/v7.1.1" }, "funding": [ { @@ -10082,20 +10079,20 @@ "type": "tidelift" } ], - "time": "2024-11-04T11:34:07+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "symfony/dependency-injection", - "version": "v7.1.8", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "e4d13f0f394f4d02a041ff76acd31c5a20a5f70b" + "reference": "38465f925ec4e0707b090e9147c65869837d639d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/e4d13f0f394f4d02a041ff76acd31c5a20a5f70b", - "reference": "e4d13f0f394f4d02a041ff76acd31c5a20a5f70b", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/38465f925ec4e0707b090e9147c65869837d639d", + "reference": "38465f925ec4e0707b090e9147c65869837d639d", "shasum": "" }, "require": { @@ -10146,7 +10143,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v7.1.8" + "source": "https://github.com/symfony/dependency-injection/tree/v7.1.5" }, "funding": [ { @@ -10162,20 +10159,20 @@ "type": "tidelift" } ], - "time": "2024-11-09T09:16:45+00:00" + "time": "2024-09-20T08:28:38+00:00" }, { "name": "symfony/filesystem", - "version": "v7.1.6", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "c835867b3c62bb05c7fe3d637c871c7ae52024d4" + "reference": "61fe0566189bf32e8cfee78335d8776f64a66f5a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/c835867b3c62bb05c7fe3d637c871c7ae52024d4", - "reference": "c835867b3c62bb05c7fe3d637c871c7ae52024d4", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/61fe0566189bf32e8cfee78335d8776f64a66f5a", + "reference": "61fe0566189bf32e8cfee78335d8776f64a66f5a", "shasum": "" }, "require": { @@ -10212,7 +10209,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.1.6" + "source": "https://github.com/symfony/filesystem/tree/v7.1.5" }, "funding": [ { @@ -10228,20 +10225,20 @@ "type": "tidelift" } ], - "time": "2024-10-25T15:11:02+00:00" + "time": "2024-09-17T09:16:35+00:00" }, { "name": "symfony/options-resolver", - "version": "v7.1.6", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "85e95eeede2d41cd146146e98c9c81d9214cae85" + "reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/85e95eeede2d41cd146146e98c9c81d9214cae85", - "reference": "85e95eeede2d41cd146146e98c9c81d9214cae85", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/47aa818121ed3950acd2b58d1d37d08a94f9bf55", + "reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55", "shasum": "" }, "require": { @@ -10279,7 +10276,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.1.6" + "source": "https://github.com/symfony/options-resolver/tree/v7.1.1" }, "funding": [ { @@ -10295,7 +10292,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "symfony/polyfill-iconv", @@ -10455,16 +10452,16 @@ }, { "name": "symfony/stopwatch", - "version": "v7.1.6", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "8b4a434e6e7faf6adedffb48783a5c75409a1a05" + "reference": "5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/8b4a434e6e7faf6adedffb48783a5c75409a1a05", - "reference": "8b4a434e6e7faf6adedffb48783a5c75409a1a05", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d", + "reference": "5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d", "shasum": "" }, "require": { @@ -10497,7 +10494,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v7.1.6" + "source": "https://github.com/symfony/stopwatch/tree/v7.1.1" }, "funding": [ { @@ -10513,20 +10510,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "symfony/var-exporter", - "version": "v7.1.6", + "version": "v7.1.2", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "90173ef89c40e7c8c616653241048705f84130ef" + "reference": "b80a669a2264609f07f1667f891dbfca25eba44c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/90173ef89c40e7c8c616653241048705f84130ef", - "reference": "90173ef89c40e7c8c616653241048705f84130ef", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/b80a669a2264609f07f1667f891dbfca25eba44c", + "reference": "b80a669a2264609f07f1667f891dbfca25eba44c", "shasum": "" }, "require": { @@ -10573,7 +10570,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v7.1.6" + "source": "https://github.com/symfony/var-exporter/tree/v7.1.2" }, "funding": [ { @@ -10589,20 +10586,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-06-28T08:00:31+00:00" }, { "name": "symfony/yaml", - "version": "v6.4.13", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "e99b4e94d124b29ee4cf3140e1b537d2dad8cec9" + "reference": "762ee56b2649659380e0ef4d592d807bc17b7971" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/e99b4e94d124b29ee4cf3140e1b537d2dad8cec9", - "reference": "e99b4e94d124b29ee4cf3140e1b537d2dad8cec9", + "url": "https://api.github.com/repos/symfony/yaml/zipball/762ee56b2649659380e0ef4d592d807bc17b7971", + "reference": "762ee56b2649659380e0ef4d592d807bc17b7971", "shasum": "" }, "require": { @@ -10645,7 +10642,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v6.4.13" + "source": "https://github.com/symfony/yaml/tree/v6.4.12" }, "funding": [ { @@ -10661,7 +10658,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2024-09-17T12:47:12+00:00" }, { "name": "theseer/tokenizer", diff --git a/config/super_cache_invalidate.php b/config/super_cache_invalidate.php deleted file mode 100644 index 7c58436..0000000 --- a/config/super_cache_invalidate.php +++ /dev/null @@ -1,73 +0,0 @@ - env('SUPERCACHE_INVALIDATE_TOTAL_SHARDS', 10), - - /* - |-------------------------------------------------------------------------- - | Invalidation Window (Seconds) - |-------------------------------------------------------------------------- - | - | The time window in seconds during which repeated invalidation events - | for the same identifier (key or tag) are suppressed to prevent - | redundant cache invalidations. - | - */ - 'invalidation_window' => env('SUPERCACHE_INVALIDATE_INVALIDATION_WINDOW', 60), - - /* - |-------------------------------------------------------------------------- - | Event Processing Limit - |-------------------------------------------------------------------------- - | - | The maximum number of events to fetch per batch when processing - | invalidation events. - | - */ - 'processing_limit' => env('SUPERCACHE_INVALIDATE_PROCESSING_LIMIT', 10000), - - /* - |-------------------------------------------------------------------------- - | Tag Batch Size - |-------------------------------------------------------------------------- - | - | The number of identifiers (keys or tags) to process in each batch - | during cache invalidation. - | - */ - 'tag_batch_size' => env('SUPERCACHE_INVALIDATE_TAG_BATCH_SIZE', 100), - - /* - |-------------------------------------------------------------------------- - | Lock Timeout (Seconds) - |-------------------------------------------------------------------------- - | - | The duration in seconds for which a shard lock is held during event - | processing to prevent overlapping processes. - | - */ - 'lock_timeout' => env('SUPERCACHE_INVALIDATE_LOCK_TIMEOUT', 600), - - /* - |-------------------------------------------------------------------------- - | Custom Invalidation Callbacks - |-------------------------------------------------------------------------- - | - | Callbacks to customize the invalidation logic for keys and tags. - | Set these to callable functions or leave as null to use default logic. - | - */ - 'key_invalidation_callback' => env('SUPERCACHE_INVALIDATE_KEY_INVALIDATION_CALLBACK', null), - 'tag_invalidation_callback' => env('SUPERCACHE_INVALIDATE_TAG_INVALIDATION_CALLBACK', null), - -]; diff --git a/config/supercache.php b/config/supercache.php new file mode 100644 index 0000000..14b89e8 --- /dev/null +++ b/config/supercache.php @@ -0,0 +1,18 @@ + env('SUPERCACHE_PREFIX', 'supercache:'), + // connessione Redis da usare + 'connection' => env('SUPERCACHE_CONNECTION', 'default'), + // Numero di shard configurato + 'num_shards' => env('SUPERCACHE_NUM_SHARDS', 256), + // Flag per abilitare/disabilitare il namespace suffix delle chiavi + 'use_namespace' => env('SUPERCACHE_USE_NAMESPACE', false), + // Numero di namespace configurabili + 'num_namespace' => env('SUPERCACHE_NUM_NAMESPACE', 16), + // Parametri per il batching del listner + 'batch_size' => env('SUPERCACHE_BATCH_SIZE', 100), + 'time_threshold' => env('SUPERCACHE_TIME_THRESHHOLD', 1), //secondi +]; + diff --git a/database/migrations/2024_11_20_200800_create_cache_invalidation_events_table.php b/database/migrations/2024_11_20_200800_create_cache_invalidation_events_table.php deleted file mode 100644 index d62836e..0000000 --- a/database/migrations/2024_11_20_200800_create_cache_invalidation_events_table.php +++ /dev/null @@ -1,98 +0,0 @@ -bigIncrements('id'); - $table->enum('type', ['key', 'tag'])->comment('Indicates whether the event is for a cache key or tag'); - $table->string('identifier')->comment('The cache key or tag to invalidate'); - $table->string('reason')->nullable()->comment('Reason for the invalidation (for logging purposes)'); - $table->tinyInteger('priority')->default(0)->comment('Priority of the event'); - $table->dateTime('event_time')->default(DB::raw('CURRENT_TIMESTAMP'))->comment('Timestamp when the event was created'); - $table->boolean('processed')->default(0)->comment('Flag indicating whether the event has been processed'); - $table->integer('shard')->comment('Shard number for parallel processing'); - - // Partition key as a generated stored column - $table->integer('partition_key')->storedAs(' - CASE - WHEN `processed` = 0 THEN - `shard` * 100 + `priority` - ELSE - `shard` * 100000000 + `priority` * 10000000 + (YEAR(`event_time`) * 100 + WEEK(`event_time`, 3)) - END - ')->comment('Partition key for efficient querying and partitioning'); - - // Indexes - $table->index(['processed', 'shard', 'priority', 'partition_key', 'event_time'], 'idx_processed_shard_priority'); - $table->index(['type', 'identifier'], 'idx_type_identifier'); - }); - - // Generate partitions using the PHP script - $partitionSQL = $this->generatePartitionSQL(); - - // Add partitioning - DB::statement("ALTER TABLE `cache_invalidation_events` PARTITION BY RANGE (`partition_key`) ( - {$partitionSQL} - );"); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('cache_invalidation_events'); - } -}; diff --git a/database/migrations/2024_11_20_200801_create_cache_invalidation_timestamps_table.php b/database/migrations/2024_11_20_200801_create_cache_invalidation_timestamps_table.php deleted file mode 100644 index 2722173..0000000 --- a/database/migrations/2024_11_20_200801_create_cache_invalidation_timestamps_table.php +++ /dev/null @@ -1,72 +0,0 @@ -enum('identifier_type', ['key', 'tag'])->comment('Indicates whether the identifier is a cache key or tag'); - $table->string('identifier')->comment('The cache key or tag'); - $table->dateTime('last_invalidated')->comment('Timestamp of the last invalidation'); - - // Partition key as a generated stored column - $table->integer('partition_key')->storedAs('YEAR(`last_invalidated`) * 100 + WEEK(`last_invalidated`, 3)')->comment('Partition key based on last_invalidated'); - - $table->primary(['identifier_type', 'identifier']); - $table->index(['identifier_type', 'identifier'], 'idx_identifier_type_identifier'); - $table->index('partition_key', 'idx_partition_key'); - }); - - // Generate partitions - $partitionSQL = $this->generatePartitionSQL(); - - // Add partitioning - DB::statement("ALTER TABLE `cache_invalidation_timestamps` PARTITION BY RANGE (`partition_key`) ( - {$partitionSQL} - );"); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('cache_invalidation_timestamps'); - } - - /** - * Generate partition SQL statements. - * - * @return string - */ - protected function generatePartitionSQL(): string - { - $startYear = 2024; - $endYear = 2050; - - $partitionStatements = []; - - for ($year = $startYear; $year <= $endYear; $year++) { - for ($week = 1; $week <= 53; $week++) { - $partitionKey = $year * 100 + $week; - $nextPartitionKey = $partitionKey + 1; - - $partitionName = "p_{$year}w{$week}"; - $partitionStatements[] = "PARTITION {$partitionName} VALUES LESS THAN ({$nextPartitionKey})"; - } - } - - // Final partition for any values beyond defined partitions - $partitionStatements[] = "PARTITION pMax VALUES LESS THAN MAXVALUE"; - - // Combine all partition statements - return implode(",\n", $partitionStatements); - } -}; diff --git a/database/migrations/2024_11_20_200802_create_cache_invalidation_event_associations_table.php b/database/migrations/2024_11_20_200802_create_cache_invalidation_event_associations_table.php deleted file mode 100644 index 6ad50b7..0000000 --- a/database/migrations/2024_11_20_200802_create_cache_invalidation_event_associations_table.php +++ /dev/null @@ -1,78 +0,0 @@ -bigIncrements('id'); - $table->unsignedBigInteger('event_id')->comment('Reference to cache_invalidation_events.id'); - $table->enum('associated_type', ['key', 'tag'])->comment('Indicates if the associated identifier is a cache key or tag'); - $table->string('associated_identifier')->comment('The associated cache key or tag'); - $table->dateTime('created_at')->default(DB::raw('CURRENT_TIMESTAMP'))->comment('Timestamp of association creation'); - - // Partition key as a generated stored column - $table->integer('partition_key')->storedAs('YEAR(`created_at`) * 100 + WEEK(`created_at`, 3)')->comment('Partition key based on created_at'); - - // Indexes - $table->index('event_id', 'idx_event_id'); - $table->index(['associated_type', 'associated_identifier'], 'idx_associated_type_identifier'); - $table->index('partition_key', 'idx_partition_key'); - - // Foreign key constraint - $table->foreign('event_id')->references('id')->on('cache_invalidation_events')->onDelete('cascade'); - }); - - // Generate partitions - $partitionSQL = $this->generatePartitionSQL(); - - // Add partitioning - DB::statement("ALTER TABLE `cache_invalidation_event_associations` PARTITION BY RANGE (`partition_key`) ( - {$partitionSQL} - );"); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('cache_invalidation_event_associations'); - } - - /** - * Generate partition SQL statements. - * - * @return string - */ - protected function generatePartitionSQL(): string - { - $startYear = 2024; - $endYear = 2050; - - $partitionStatements = []; - - for ($year = $startYear; $year <= $endYear; $year++) { - for ($week = 1; $week <= 53; $week++) { - $partitionKey = $year * 100 + $week; - $nextPartitionKey = $partitionKey + 1; - - $partitionName = "p_{$year}w{$week}"; - $partitionStatements[] = "PARTITION {$partitionName} VALUES LESS THAN ({$nextPartitionKey})"; - } - } - - // Final partition for any values beyond defined partitions - $partitionStatements[] = "PARTITION pMax VALUES LESS THAN MAXVALUE"; - - // Combine all partition statements - return implode(",\n", $partitionStatements); - } -}; diff --git a/src/Console/CleanOrphanedKeysCommand.php b/src/Console/CleanOrphanedKeysCommand.php new file mode 100644 index 0000000..6e5fa79 --- /dev/null +++ b/src/Console/CleanOrphanedKeysCommand.php @@ -0,0 +1,80 @@ +redis = $redis; + $this->numShards = (int) config('supercache.num_shards'); // Numero di shard configurato + } + + public function handle(): void + { + $this->info('Starting orphaned keys cleanup...'); + + // Carica il prefisso di default per le chiavi + $prefix = config('supercache.prefix'); + + // Script Lua per pulire le chiavi orfane + $script = <<redis->getRedis()->eval( + $script, + 4, // Numero di parametri passati a Lua come KEYS + $shardPrefix, + $this->numShards, + $lockKey, + $lockTTL + ); + + $this->info($result); + } +} diff --git a/src/Console/GetAllTagsOfKeyCommand.php b/src/Console/GetAllTagsOfKeyCommand.php new file mode 100644 index 0000000..02bcd02 --- /dev/null +++ b/src/Console/GetAllTagsOfKeyCommand.php @@ -0,0 +1,27 @@ +cacheManager = $cacheManager; + } + + public function handle() + { + $key = $this->argument('key'); + $tags = $this->cacheManager->getTagsOfKey($key); + $this->info('Tags for key ' . $key . ': ' . implode(', ', $tags)); + } +} diff --git a/src/Console/ListenerCommand.php b/src/Console/ListenerCommand.php new file mode 100644 index 0000000..7405a48 --- /dev/null +++ b/src/Console/ListenerCommand.php @@ -0,0 +1,147 @@ +redis = $redis; + + // Parametri di batch processing da config + $this->batchSizeThreshold = config('supercache.batch_size'); + $this->timeThreshold = config('supercache.time_threshold'); // secondi + } + + public function handle() + { + $namespace = $this->argument('namespace'); // Recupera il namespace passato come argomento + $this->info('Avviando il listener di scadenza Redis per il namespace: ' . $namespace); + + if (!$this->checkRedisNotifications()) { + $this->error('Le notifiche di scadenza di Redis non sono abilitate. Abilitale per usare il listener.'); + return; + } + + // Pattern per ascoltare solo gli eventi che appartengono al namespace specificato + $pattern = "__keyevent@0__:expired:{$namespace}*"; + + // Sottoscrizione agli eventi di scadenza + $this->redis->getRedis()->psubscribe([$pattern], function ($message, $key) use ($namespace) { + $this->onExpireEvent($key); + + // Verifica se è necessario processare il batch + if (count($this->batch) >= $this->batchSizeThreshold || $this->shouldProcessBatchByTime()) { + $this->processBatch(); + } + }); + } + + /** + * Aggiunge la chiave scaduta al batch corrente. + */ + protected function onExpireEvent(string $key): void + { + $this->batch[] = $key; + } + + /** + * Verifica se è passato abbastanza tempo da processare il batch. + */ + protected function shouldProcessBatchByTime(): bool + { + static $lastBatchTime = null; + if (!$lastBatchTime) { + $lastBatchTime = time(); + return false; + } + + if ((time() - $lastBatchTime) >= $this->timeThreshold) { + $lastBatchTime = time(); + return true; + } + + return false; + } + + /** + * Processa le chiavi accumulate in batch tramite uno script Lua. + */ + protected function processBatch(): void + { + $luaScript = <<redis->getRedis()->eval( + $luaScript, + // KEYS: prefix e numero di shard + 2, + config('supercache.prefix'), + config('supercache.num_shards'), + // ARGV: le chiavi del batch + ...$this->batch + ); + + // Pulisce il batch dopo il successo + $this->batch = []; + } catch (\Exception $e) { + Log::error('Errore nel processare il batch con Lua: ' . $e->getMessage()); + // Qui puoi implementare una logica di retry o DLQ + } + } + + /** + * Verifica se Redis è configurato per generare notifiche di scadenza. + */ + protected function checkRedisNotifications(): bool + { + $config = $this->redis->getRedis()->config('GET', 'notify-keyspace-events'); + return str_contains($config['notify-keyspace-events'], 'Ex'); + } +} diff --git a/src/Console/ProcessCacheInvalidationEventsCommand.php b/src/Console/ProcessCacheInvalidationEventsCommand.php deleted file mode 100644 index 96bea3b..0000000 --- a/src/Console/ProcessCacheInvalidationEventsCommand.php +++ /dev/null @@ -1,413 +0,0 @@ -helper = $helper; - } - - /** - * Process cache invalidation events. - * - * @param int $shardId The shard number to process - * @param int $priority The priority level - * @param int $limit Maximum number of events to fetch per batch - * @param int $tagBatchSize Number of identifiers to process per batch - * - * @throws \Exception - */ - protected function processEvents(int $shardId, int $priority, int $limit, int $tagBatchSize): void - { - $processingStartTime = now(); - $invalidationWindow = config('super_cache_invalidate.invalidation_window'); - - // Fetch a batch of unprocessed events - $events = DB::table('cache_invalidation_events') - ->where('processed', 0) - ->where('shard', $shardId) - ->where('priority', $priority) - ->where('event_time', '<', $processingStartTime) - ->orderBy('event_time') - ->limit($limit) - ->get() - ; - - if ($events->isEmpty()) { - // No more events to process - return; - } - - // Group events by type and identifier - $eventsByIdentifier = $events->groupBy(function ($event) { - return $event->type . ':' . $event->identifier; - }); - - $batchIdentifiers = []; - $eventsToUpdate = []; - $counter = 0; - - // Fetch associated identifiers for the events - $eventIds = $events->pluck('id')->all(); - - //retrive associated identifiers related to fetched event id - $associations = DB::table('cache_invalidation_event_associations') - ->whereIn('event_id', $eventIds) - ->get() - ->groupBy('event_id') - ; - - // Prepare list of all identifiers to fetch last invalidation times - $allIdentifiers = []; - - foreach ($eventsByIdentifier as $key => $eventsGroup) { - $allIdentifiers[] = $key; - foreach ($eventsGroup as $event) { - $eventAssociations = $associations->get($event->id, collect()); - foreach ($eventAssociations as $assoc) { - $assocKey = $assoc->associated_type . ':' . $assoc->associated_identifier; - $allIdentifiers[] = $assocKey; - } - } - } - - // Fetch last invalidation times in bulk - $lastInvalidationTimes = $this->getLastInvalidationTimes(array_unique($allIdentifiers)); - - foreach ($eventsByIdentifier as $key => $eventsGroup) { - // Extract type and identifier - [$type, $identifier] = explode(':', $key, 2); - - // Get associated identifiers for the events - $associatedIdentifiers = []; - foreach ($eventsGroup as $event) { - $eventAssociations = $associations->get($event->id, collect()); - foreach ($eventAssociations as $assoc) { - $assocKey = $assoc->associated_type . ':' . $assoc->associated_identifier; - $associatedIdentifiers[$assocKey] = [ - 'type' => $assoc->associated_type, - 'identifier' => $assoc->associated_identifier, - ]; - } - } - - // Build a list of all identifiers to check - $identifiersToCheck = [$key]; - $identifiersToCheck = array_merge($identifiersToCheck, array_keys($associatedIdentifiers)); - - $lastInvalidationTimesSubset = array_intersect_key($lastInvalidationTimes, array_flip($identifiersToCheck)); - - $shouldInvalidate = $this->shouldInvalidateMultiple($identifiersToCheck, $lastInvalidationTimesSubset, $invalidationWindow); - - if ($shouldInvalidate) { - // Proceed to invalidate - $latestEvent = $eventsGroup->last(); - - // Accumulate identifiers and events - $batchIdentifiers[] = [ - 'type' => $type, - 'identifier' => $identifier, - 'event' => $latestEvent, - 'associated' => array_values($associatedIdentifiers), - ]; - - // Update last invalidation times for all identifiers - $this->updateLastInvalidationTimes($identifiersToCheck); - - // Mark all events in the group as processed - foreach ($eventsGroup as $event) { - $eventsToUpdate[] = $event->id; - } - } else { - // Within the invalidation window, skip invalidation - // Mark all events except the last one as processed - $eventsToProcess = $eventsGroup->slice(0, -1); - foreach ($eventsToProcess as $event) { - $eventsToUpdate[] = $event->id; - } - // The last event remains unprocessed - } - - $counter++; - - // When we reach the batch size, process the accumulated identifiers - if ($counter % $tagBatchSize === 0) { - $this->processBatch($batchIdentifiers, $eventsToUpdate); - - // Reset the accumulators - $batchIdentifiers = []; - $eventsToUpdate = []; - } - } - - // Process any remaining identifiers in the batch - if (!empty($batchIdentifiers)) { - $this->processBatch($batchIdentifiers, $eventsToUpdate); - } - } - - /** - * Fetch last invalidation times for identifiers in bulk. - * - * @param array $identifiers Array of 'type:identifier' strings - * @return array Associative array of last invalidation times - */ - protected function getLastInvalidationTimes(array $identifiers): array - { - // Extract types and identifiers into tuples - $tuples = array_map(static function ($key) { - return explode(':', $key, 2); - }, $identifiers); - - if (empty($tuples)) { - return []; - } - - // Prepare placeholders and parameters - $placeholders = implode(',', array_fill(0, count($tuples), '(?, ?)')); - $params = []; - foreach ($tuples as [$type, $identifier]) { - $params[] = $type; - $params[] = $identifier; - } - - // Build and execute the query - $sql = "SELECT identifier_type, - identifier, - last_invalidated - FROM cache_invalidation_timestamps - WHERE (identifier_type, identifier) IN ($placeholders) - "; - $records = DB::select($sql, $params); - - // Build associative array - $lastInvalidationTimes = []; - foreach ($records as $record) { - $key = $record->identifier_type . ':' . $record->identifier; - $lastInvalidationTimes[$key] = Carbon::parse($record->last_invalidated); - } - - return $lastInvalidationTimes; - } - - /** - * Determine whether to invalidate based on last invalidation times for multiple identifiers. - * - * @param array $identifiers Array of 'type:identifier' strings - * @param array $lastInvalidationTimes Associative array of last invalidation times - * @param int $invalidationWindow Invalidation window in seconds - * @return bool True if should invalidate, false otherwise - */ - protected function shouldInvalidateMultiple(array $identifiers, array $lastInvalidationTimes, int $invalidationWindow): bool - { - $now = now(); - foreach ($identifiers as $key) { - $lastInvalidated = $lastInvalidationTimes[$key] ?? null; - if (!$lastInvalidated) { - continue; - } - $elapsed = $now->diffInSeconds($lastInvalidated); - if ($elapsed < $invalidationWindow) { - // At least one identifier is within the invalidation window - return false; - } - } - - // All identifiers are outside the invalidation window or have no record - return true; - } - - /** - * Update the last invalidation times for multiple identifiers. - * - * @param array $identifiers Array of 'type:identifier' strings - */ - protected function updateLastInvalidationTimes(array $identifiers): void - { - $now = now(); - - foreach ($identifiers as $key) { - [$type, $identifier] = explode(':', $key, 2); - DB::table('cache_invalidation_timestamps') - ->updateOrInsert( - ['identifier_type' => $type, 'identifier' => $identifier], - ['last_invalidated' => $now] - ) - ; - } - } - - /** - * Process a batch of identifiers and update events. - * - * @param array $batchIdentifiers Array of identifiers to invalidate - * @param array $eventsToUpdate Array of event IDs to mark as processed - * - * @throws \Exception - */ - protected function processBatch(array $batchIdentifiers, array $eventsToUpdate): void - { - // Begin transaction for the batch - DB::beginTransaction(); - - try { - // Separate keys and tags - $keys = []; - $tags = []; - foreach ($batchIdentifiers as $item) { - if ($item['type'] === 'key') { - $keys[] = $item['identifier']; - } else { - $tags[] = $item['identifier']; - } - - // Include associated identifiers - if (!empty($item['associated'])) { - foreach ($item['associated'] as $assoc) { - if ($assoc['type'] === 'key') { - $keys[] = $assoc['identifier']; - } else { - $tags[] = $assoc['identifier']; - } - } - } - } - - // Remove duplicates - $keys = array_unique($keys); - $tags = array_unique($tags); - - // Invalidate cache for keys - if (!empty($keys)) { - $this->invalidateKeys($keys); - } - - // Invalidate cache for tags - if (!empty($tags)) { - $this->invalidateTags($tags); - } - - // Mark events as processed - DB::table('cache_invalidation_events') - ->whereIn('id', $eventsToUpdate) - ->update(['processed' => 1]) - ; - - // Commit transaction - DB::commit(); - } catch (\Exception $e) { - // Rollback transaction on error - DB::rollBack(); - throw $e; - } - } - - /** - * Invalidate cache keys. - * - * @param array $keys Array of cache keys to invalidate - */ - protected function invalidateKeys(array $keys): void - { - $callback = config('super_cache_invalidate.key_invalidation_callback'); - - if (is_callable($callback)) { - call_user_func($callback, $keys); - - return; - } - - //default invalidation method - foreach ($keys as $key) { - Cache::forget($key); - } - } - - /** - * Invalidate cache tags. - * - * @param array $tags Array of cache tags to invalidate - */ - protected function invalidateTags(array $tags): void - { - $callback = config('super_cache_invalidate.tag_invalidation_callback'); - - if (is_callable($callback)) { - call_user_func($callback, $tags); - - return; - } - - //default invalidation method - Cache::tags($tags)->flush(); - } - - /** - * Execute the console command. - */ - public function handle(): void - { - $shardId = (int) $this->option('shard'); - $priority = (int) $this->option('priority'); - $limit = $this->option('limit') ?? config('super_cache_invalidate.processing_limit'); - $tagBatchSize = $this->option('tag-batch-size') ?? config('super_cache_invalidate.tag_batch_size'); - $lockTimeout = config('super_cache_invalidate.lock_timeout'); - - if ($shardId === 0 && $priority === 0) { - $this->error('Shard and priority are required and must be non-zero integers.'); - - return; - } - - $lockValue = $this->helper->acquireShardLock($shardId, $lockTimeout); - - if (!$lockValue) { - $this->info("Another process is handling shard $shardId."); - - return; - } - - try { - $this->processEvents($shardId, $priority, $limit, $tagBatchSize); - } catch (\Exception $e) { - $this->error('An error occourred in ' . __METHOD__ . ': ' . $e->getTraceAsString()); - } finally { - $this->helper->releaseShardLock($shardId, $lockValue); - } - } -} diff --git a/src/Console/PruneCacheInvalidationDataCommand.php b/src/Console/PruneCacheInvalidationDataCommand.php deleted file mode 100644 index 5608a11..0000000 --- a/src/Console/PruneCacheInvalidationDataCommand.php +++ /dev/null @@ -1,80 +0,0 @@ -info("No partitions to prune for table {$tableName}."); - - return; - } - - // Build DROP PARTITION statement - $partitionNames = implode(', ', array_map(function ($partition) { - return $partition->PARTITION_NAME; - }, $partitions)); - - DB::statement("ALTER TABLE `{$tableName}` DROP PARTITION {$partitionNames}"); - $this->info("Pruned partitions: {$partitionNames} from table {$tableName}."); - } - - /** - * Execute the console command. - */ - public function handle(): void - { - $months = (int) $this->option('months'); - $retentionDate = now()->subMonths($months); - $retentionPartitionKey = $retentionDate->year * 100 + $retentionDate->week(); - - // Prune tables - $tables = [ - 'cache_invalidation_events', - 'cache_invalidation_timestamps', - 'cache_invalidation_event_associations', - ]; - - foreach ($tables as $tableName) { - $this->pruneTable($tableName, $retentionPartitionKey); - } - - $this->info('Old cache invalidation data has been pruned.'); - } -} diff --git a/src/Events/SuperCacheInvalidationEvent.php b/src/Events/SuperCacheInvalidationEvent.php deleted file mode 100644 index 7dd3f9f..0000000 --- a/src/Events/SuperCacheInvalidationEvent.php +++ /dev/null @@ -1,47 +0,0 @@ -type = $type; - $this->identifier = $identifier; - $this->reason = $reason; - } -} diff --git a/src/Facades/SuperCacheFacade.php b/src/Facades/SuperCacheFacade.php new file mode 100644 index 0000000..14b0f35 --- /dev/null +++ b/src/Facades/SuperCacheFacade.php @@ -0,0 +1,26 @@ + $type, - 'identifier' => $identifier, - 'reason' => $reason, - 'priority' => $priority, - 'event_time' => now(), - 'processed' => 0, - 'shard' => $shard, - ]; - - // Insert the event and get its ID - $eventId = DB::table('cache_invalidation_events')->insertGetId($data); - - // Insert associated identifiers - if (!empty($associatedIdentifiers)) { - $associations = []; - foreach ($associatedIdentifiers as $associated) { - $associations[] = [ - 'event_id' => $eventId, - 'associated_type' => $associated['type'], // 'key' or 'tag' - 'associated_identifier' => $associated['identifier'], - 'created_at' => now(), - ]; - } - DB::table('cache_invalidation_event_associations')->insert($associations); - } - } - - /** - * Acquire a lock for processing a shard. - * - * @param int $shardId The shard number - * @param int $lockTimeout Lock timeout in seconds - * @return string|false The lock value if acquired, false otherwise - */ - public function acquireShardLock(int $shardId, int $lockTimeout): bool|string - { - $lockKey = "shard_lock:$shardId"; - $lockValue = uniqid('', true); - $isLocked = Redis::set($lockKey, $lockValue, 'NX', 'EX', $lockTimeout); - - return $isLocked ? $lockValue : false; - } - - /** - * Release the lock for a shard. - * - * @param int $shardId The shard number - * @param string $lockValue The lock value to validate ownership - */ - public function releaseShardLock(int $shardId, string $lockValue): void - { - $lockKey = "shard_lock:$shardId"; - $currentValue = Redis::get($lockKey); - if ($currentValue === $lockValue) { - Redis::del($lockKey); - } - } -} diff --git a/src/RedisConnector.php b/src/RedisConnector.php new file mode 100644 index 0000000..46c8843 --- /dev/null +++ b/src/RedisConnector.php @@ -0,0 +1,26 @@ +connection = config('supercache.connection'); + } + + public function getRedis() + { + return Redis::connection($this->connection); + } + + // Metodo per ottimizzare le operazioni Redis con pipeline + public function pipeline($callback) + { + return $this->getRedis()->pipeline($callback); + } +} diff --git a/src/SuperCacheInvalidateServiceProvider.php b/src/SuperCacheInvalidateServiceProvider.php deleted file mode 100644 index 7ef1f8c..0000000 --- a/src/SuperCacheInvalidateServiceProvider.php +++ /dev/null @@ -1,50 +0,0 @@ -mergeConfigFrom( - __DIR__ . '/../../config/super_cache_invalidate.php', - 'super_cache_invalidate' - ); - - // Register the helper as a singleton - $this->app->singleton('supercache.invalidation', function ($app) { - return new SuperCacheInvalidationHelper(); - }); - } - - /** - * Perform post-registration booting of services. - */ - public function boot(): void - { - // Publish configuration - $this->publishes([ - __DIR__ . '/../../config/super_cache_invalidate.php' => config_path('super_cache_invalidate.php'), - ], 'config'); - - // Publish migrations - $this->loadMigrationsFrom(__DIR__ . '/../../database/migrations'); - - // Register commands - if ($this->app->runningInConsole()) { - $this->commands([ - ProcessCacheInvalidationEventsCommand::class, - PruneCacheInvalidationDataCommand::class, - ]); - } - } -} diff --git a/src/SuperCacheManager.php b/src/SuperCacheManager.php new file mode 100644 index 0000000..3f3cd82 --- /dev/null +++ b/src/SuperCacheManager.php @@ -0,0 +1,256 @@ +redis = $redis; + $this->prefix = config('supercache.prefix'); + $this->numShards = (int) config('supercache.num_shards'); // Numero di shard per tag + $this->useNamespace = (bool) config('supercache.use_namespace', false); // Flag per abilitare/disabilitare il namespace + } + + /** + * Salva un valore nella cache senza tag. + */ + public function put(string $key, mixed $value, ?int $ttl = null): void + { + // Calcola la chiave con o senza namespace in base alla configurazione + $finalKey = $this->getFinalKey($key); + + $this->redis->getRedis()->set($finalKey, serialize($value)); + + if ($ttl !== null) { + $this->redis->getRedis()->expire($finalKey, $ttl); + } + } + + /** + * Salva un valore nella cache con uno o più tag. + */ + public function putWithTags(string $key, mixed $value, array $tags, ?int $ttl = null): void + { + $finalKey = $this->getFinalKey($key); + + // Usa pipeline solo se non è un cluster + if (!$this->isCluster) { + $this->redis->pipeline(function ($pipe) use ($finalKey, $value, $tags, $ttl) { + $pipe->set($finalKey, serialize($value)); + + if ($ttl !== null) { + $pipe->expire($finalKey, $ttl); + } + + foreach ($tags as $tag) { + $shard = $this->getShardNameForTag($tag, $finalKey); + $pipe->sadd($shard, $finalKey); + } + + $pipe->sadd($this->prefix . 'tags:' . $finalKey, ...$tags); + }); + } else { + $this->redis->getRedis()->set($finalKey, serialize($value)); + if ($ttl !== null) { + $this->redis->getRedis()->expire($finalKey, $ttl); + } + + foreach ($tags as $tag) { + $shard = $this->getShardNameForTag($tag, $finalKey); + $this->redis->getRedis()->sadd($shard, $finalKey); + } + + $this->redis->getRedis()->sadd($this->prefix . 'tags:' . $finalKey, ...$tags); + } + } + + /** + * Recupera un valore dalla cache. + */ + public function get(string $key): mixed + { + $finalKey = $this->getFinalKey($key); + $value = $this->redis->getRedis()->get($finalKey); + return $value ? unserialize($value) : null; + } + + /** + * Rimuove una chiave dalla cache e dai suoi set di tag. + */ + public function forget(string $key): void + { + $finalKey = $this->getFinalKey($key); + + // Recupera i tag associati alla chiave + $tags = $this->redis->getRedis()->smembers($this->prefix . 'tags:' . $finalKey); + + if (!$this->isCluster) { + $this->redis->pipeline(function ($pipe) use ($tags, $finalKey) { + foreach ($tags as $tag) { + $shard = $this->getShardNameForTag($tag, $finalKey); + $pipe->srem($shard, $finalKey); + } + + $pipe->del($this->prefix . 'tags:' . $finalKey); + $pipe->del($finalKey); + }); + } else { + foreach ($tags as $tag) { + $shard = $this->getShardNameForTag($tag, $finalKey); + $this->redis->getRedis()->srem($shard, $finalKey); + } + + $this->redis->getRedis()->del($this->prefix . 'tags:' . $finalKey); + $this->redis->getRedis()->del($finalKey); + } + } + + /** + * Recupera tutti i tag associati a una chiave. + */ + public function getTagsOfKey(string $key): array + { + $finalKey = $this->getFinalKey($key); + return $this->redis->getRedis()->smembers($this->prefix . 'tags:' . $finalKey); + } + + /** + * Recupera tutte le chiavi associate a un tag. + */ + public function getKeysOfTag(string $tag): array + { + $keys = []; + + // Itera attraverso tutti gli shard del tag + for ($i = 0; $i < $this->numShards; $i++) { + $shard = $this->prefix . 'tag:' . $tag . ':shard:' . $i; + $keys = array_merge($keys, $this->redis->getRedis()->smembers($shard)); + } + + return $keys; + } + + /** + * Ritorna il nome dello shard per una chiave e un tag. + */ + public function getShardNameForTag(string $tag, string $key): string + { + // Usa la funzione hash per calcolare lo shard della chiave + $hash = \xxHash32::hash($key); + $shardIndex = $hash % $this->numShards; + + return $this->prefix . 'tag:' . $tag . ':shard:' . $shardIndex; + } + + /** + * Aggiunge il namespace come suffisso alla chiave se abilitato. + * + * Se l'opzione 'use_namespace' è disattivata, la chiave sarà formata senza namespace. + */ + public function getFinalKey(string $key): string + { + // Se il namespace è abilitato, calcola la chiave con namespace come suffisso + if ($this->useNamespace) { + $namespace = $this->calculateNamespace($key); + return $this->prefix . $key . ':' . $namespace; + } + + // Se il namespace è disabilitato, usa la chiave senza suffisso + return $this->prefix . $key; + } + + /** + * Calcola il namespace in base alla chiave. + */ + protected function calculateNamespace(string $key): string + { + // Usa una funzione hash per ottenere un namespace coerente per la chiave + $hash = \xxHash32::hash($key); + $numNamespaces = (int) config('supercache.num_namespace', 16); // Numero di namespace configurabili + $namespaceIndex = $hash % $numNamespaces; + + return 'ns' . $namespaceIndex; // Ad esempio, 'ns0', 'ns1', ..., 'ns15' + } + + /** + * Flush all cache entries. + */ + public function flush(): void + { + $this->redis->getRedis()->flushall(); // Svuota tutto il database Redis + } + + /** + * Check if a cache key exists without retrieving the value. + * + * @param string $key + * @return bool + */ + public function has(string $key): bool + { + $finalKey = $this->getFinalKey($key); + return $this->redis->getRedis()->exists($finalKey) > 0; + } + + /** + * Increment a cache key by a given amount. + * If the key does not exist, creates it with the increment value. + * + * @param string $key + * @param int $increment + * @return int The new value after incrementing. + */ + public function increment(string $key, int $increment = 1): int + { + $finalKey = $this->getFinalKey($key); + return $this->redis->getRedis()->incrby($finalKey, $increment); + } + + /** + * Decrement a cache key by a given amount. + * If the key does not exist, creates it with the negative decrement value. + * + * @param string $key + * @param int $decrement + * @return int The new value after decrementing. + */ + public function decrement(string $key, int $decrement = 1): int + { + $finalKey = $this->getFinalKey($key); + return $this->redis->getRedis()->decrby($finalKey, $decrement); + } + + /** + * Get all keys matching given patterns. + * + * @param array $patterns An array of patterns (e.g. ["product:*"]) + * @return array Array of key-value pairs. + */ + public function getKeys(array $patterns): array + { + $results = []; + foreach ($patterns as $pattern) { + // Trova le chiavi che corrispondono al pattern usando SCAN + $keys = $this->redis->getRedis()->scan(null, ['MATCH' => $this->prefix . $pattern]); + + // Recupera i valori delle chiavi trovate + if ($keys) { + foreach ($keys as $key) { + $results[$key] = $this->redis->getRedis()->get($key); + } + } + } + + return $results; + } +} diff --git a/src/SuperCacheServiceProvider.php b/src/SuperCacheServiceProvider.php new file mode 100644 index 0000000..3ec0aa5 --- /dev/null +++ b/src/SuperCacheServiceProvider.php @@ -0,0 +1,37 @@ +app->singleton(SuperCacheManager::class, function ($app) { + return new SuperCacheManager(new RedisConnector()); + }); + + $this->app->singleton('supercache', function ($app) { + return new SuperCacheManager(new RedisConnector()); + }); + } + + public function boot() + { + // Carica configurazione + $this->publishes([ + __DIR__.'/../../config/supercache.php' => config_path('supercache.php'), + ], 'config'); + + // Registra i comandi + if ($this->app->runningInConsole()) { + $this->commands([ + Padosoft\SuperCache\Console\GetAllTagsOfKeyCommand::class, + Padosoft\SuperCache\Console\Listener::class, + Padosoft\SuperCache\Console\CleanOrphanedKeysCommand::class, + ]); + } + } +} diff --git a/src/Traits/ManagesLocksAndShardsTrait.php b/src/Traits/ManagesLocksAndShardsTrait.php new file mode 100644 index 0000000..b916a05 --- /dev/null +++ b/src/Traits/ManagesLocksAndShardsTrait.php @@ -0,0 +1,39 @@ +redis->getRedis()->set($lockKey, '1', 'NX', 'EX', 10); + } + + /** + * Rilascia un lock ottimistico su una chiave. + */ + protected function releaseLock(string $key): void + { + $lockKey = 'lock:' . $key; + $this->redis->getRedis()->del($lockKey); + } + + /** + * Ricava il nome dello shard per una chiave e un tag. + */ + protected function getShardNameForTag(string $tag, string $key): string + { + // Usa lo stesso algoritmo di sharding della cache manager + $hash = \xxHash32::hash($key); + $numShards = (int) config('supercache.num_shards'); + $shardIndex = $hash % $numShards; + + return config('supercache.prefix') . 'tag:' . $tag . ':shard:' . $shardIndex; + } +} + diff --git a/tests/Unit/ProcessCacheInvalidationEventsTest.php b/tests/Unit/ProcessCacheInvalidationEventsTest.php deleted file mode 100644 index b147201..0000000 --- a/tests/Unit/ProcessCacheInvalidationEventsTest.php +++ /dev/null @@ -1,80 +0,0 @@ -command = new ProcessCacheInvalidationEventsCommand($helper); - } - - public function testProcessEventsWithAssociatedIdentifiersWithinWindow(): void - { - // Mock data - $events = collect([ - (object)[ - 'id' => 1, - 'type' => 'tag', - 'identifier' => 'article_ID:7', - 'event_time' => Carbon::now()->subSeconds(10), - ], - ]); - - $associations = collect([ - (object)[ - 'event_id' => 1, - 'associated_type' => 'tag', - 'associated_identifier' => 'plp:sport', - ], - ]); - - // Mock DB queries - DB::shouldReceive('table->where->where->where->where->orderBy->limit->get') - ->andReturn($events) - ; - - DB::shouldReceive('table->whereIn->get') - ->andReturn($associations) - ; - - // Mock last invalidation times - DB::shouldReceive('select')->andReturn([ - (object)[ - 'identifier_type' => 'tag', - 'identifier' => 'article_ID:7', - 'last_invalidated' => Carbon::now()->subSeconds(40)->toDateTimeString(), - ], - (object)[ - 'identifier_type' => 'tag', - 'identifier' => 'plp:sport', - 'last_invalidated' => Carbon::now()->subSeconds(20)->toDateTimeString(), - ], - ]); - - // Mock Cache - Cache::shouldReceive('tags->flush')->never(); - - // Mock update or insert - DB::shouldReceive('table->updateOrInsert')->never(); - - // Mock event update - DB::shouldReceive('table->whereIn->update')->once(); - - // Run the command - $this->command->handle(); - - // Assertions are handled by Mockery expectations - } -} diff --git a/tests/Unit/PruneCacheInvalidationDataTest.php b/tests/Unit/PruneCacheInvalidationDataTest.php deleted file mode 100644 index 4db689d..0000000 --- a/tests/Unit/PruneCacheInvalidationDataTest.php +++ /dev/null @@ -1,39 +0,0 @@ -command = new PruneCacheInvalidationDataCommand(); - } - - public function testPruneTables(): void - { - // Mock DB queries - DB::shouldReceive('select') - ->times(3) - ->andReturn([ - (object)[ - 'PARTITION_NAME' => 'p202401', - 'PARTITION_DESCRIPTION' => '202401', - ], - ]) - ; - - DB::shouldReceive('statement')->times(3); - - // Run the command - $this->command->handle(); - - // Assertions are handled by Mockery expectations - } -} diff --git a/tests/Unit/SuperCacheInvalidationHelperTest.php b/tests/Unit/SuperCacheInvalidationHelperTest.php deleted file mode 100644 index 6afadf9..0000000 --- a/tests/Unit/SuperCacheInvalidationHelperTest.php +++ /dev/null @@ -1,47 +0,0 @@ -helper = new SuperCacheInvalidationHelper(); - } - - public function testInsertInvalidationEvent(): void - { - // Mock DB insert - DB::shouldReceive('table->insertGetId')->once()->andReturn(1); - - $this->helper->insertInvalidationEvent('tag', 'test_tag', 'Test reason', 0); - - // Assertions are handled by Mockery expectations - } - - public function testInsertInvalidationEventWithAssociations(): void - { - // Mock DB insert - DB::shouldReceive('table->insertGetId')->once()->andReturn(1); - DB::shouldReceive('table->insert')->once(); - - $this->helper->insertInvalidationEvent( - 'tag', - 'article_ID:7', - 'Article 7 removed', - 0, - [ - ['type' => 'tag', 'identifier' => 'plp:sport'], - ] - ); - - // Assertions are handled by Mockery expectations - } -} diff --git a/tests/Unit/SuperCacheManagerTest.php b/tests/Unit/SuperCacheManagerTest.php new file mode 100644 index 0000000..e735f45 --- /dev/null +++ b/tests/Unit/SuperCacheManagerTest.php @@ -0,0 +1,215 @@ +redis = $this->createMock(RedisConnector::class); + $this->superCache = new SuperCacheManager($this->redis); + + // Mock per Redis + $redisMock = $this->createMock(\Redis::class); + $this->redis->method('getRedis')->willReturn($redisMock); + } + protected function tearDown(): void + { + // Pulisce tutte le chiavi di test + $this->redis->getRedis()->flushall(); + parent::tearDown(); + } + + /** + * Data provider per il metodo `put` + */ + public function putDataProvider(): array + { + return [ + // Caso con namespace abilitato + ['key1', 'value1', 3600, true, true], + // Caso senza namespace + ['key2', 'value2', null, true, false], + // Caso con array e TTL + ['key3', ['array_value'], 600, true, true], + ]; + } + + /** + * Testa il metodo `put` + * + * @dataProvider putDataProvider + */ + public function testPut(string $key, mixed $value, ?int $ttl, bool $expected, bool $namespaceEnabled): void + { + // Configura se usare o meno il namespace + $this->superCache->useNamespace = $namespaceEnabled; + + $redis = $this->redis->getRedis(); + $finalKey = $this->superCache->getFinalKey($key); + + // Mock Redis set e expire + $redis->expects($this->once())->method('set')->with($finalKey, serialize($value))->willReturn(true); + + if ($ttl !== null) { + $redis->expects($this->once())->method('expire')->with($finalKey, $ttl)->willReturn(true); + } + + $result = $this->superCache->put($key, $value, $ttl); + $this->assertEquals($expected, $result); + } + + /** + * Data provider per il metodo `putWithTags` + */ + public static function putWithTagsDataProvider(): array + { + return [ + // Caso con namespace e un singolo tag + ['key1', 'value1', ['tag1'], 3600, true, true], + // Caso senza namespace e più tag + ['key2', 'value2', ['tag2', 'tag3'], null, true, false], + // Caso con array, TTL e namespace + ['key3', ['array_value'], ['tag4'], 600, true, true], + ]; + } + + /** + * Testa il metodo `putWithTags` + * + * @dataProvider putWithTagsDataProvider + */ + public function testPutWithTags(string $key, mixed $value, array $tags, ?int $ttl, bool $expected, bool $namespaceEnabled): void + { + // Configura se usare o meno il namespace + $this->superCache->useNamespace = $namespaceEnabled; + + $redis = $this->redis->getRedis(); + $finalKey = $this->superCache->getFinalKey($key); + + // Mock Redis set, expire, sadd + $redis->expects($this->once())->method('set')->with($finalKey, serialize($value))->willReturn(true); + + if ($ttl !== null) { + $redis->expects($this->once())->method('expire')->with($finalKey, $ttl)->willReturn(true); + } + + foreach ($tags as $tag) { + $shard = $this->superCache->getShardNameForTag($tag, $key); + $redis->expects($this->once())->method('sadd')->with($shard, $finalKey); + } + + $redis->expects($this->once())->method('sadd')->with($this->superCache->prefix . 'tags:' . $finalKey, ...$tags); + + $result = $this->superCache->putWithTags($key, $value, $tags, $ttl); + $this->assertEquals($expected, $result); + } + + /** + * Data provider per il metodo `forget` + */ + public static function forgetDataProvider(): array + { + return [ + // Caso con un tag + ['key1', ['tag1'], true], + // Caso con più tag + ['key2', ['tag2', 'tag3'], true], + // Caso senza tag + ['key3', [], true], + ]; + } + + /** + * Testa il metodo `forget` + * + * @dataProvider forgetDataProvider + */ + public function testForget(string $key, array $tags, bool $namespaceEnabled): void + { + // Configura se usare o meno il namespace + $this->superCache->useNamespace = $namespaceEnabled; + + $redis = $this->redis->getRedis(); + $finalKey = $this->superCache->getFinalKey($key); + + // Mock Redis smembers, srem, del + $redis->expects($this->once())->method('smembers')->with($this->superCache->prefix . 'tags:' . $finalKey)->willReturn($tags); + + foreach ($tags as $tag) { + $shard = $this->superCache->getShardNameForTag($tag, $finalKey); + $redis->expects($this->once())->method('srem')->with($shard, $finalKey); + } + + $redis->expects($this->once())->method('del')->with($this->superCache->prefix . 'tags:' . $finalKey); + $redis->expects($this->once())->method('del')->with($finalKey); + + $this->superCache->forget($key); + $this->assertTrue(true); // Se non ci sono errori, il test è passato + } + public function testFlush(): void + { + $this->superCache->put('key1', 'value1'); + $this->superCache->flush(); + + $this->assertFalse($this->superCache->has('key1')); + } + + public function testHas(): void + { + $this->superCache->put('key1', 'value1'); + + $this->assertTrue($this->superCache->has('key1')); + $this->assertFalse($this->superCache->has('non_existing_key')); + } + + public function testIncrement(): void + { + // Incrementa una chiave che non esiste (crea con valore 5) + $newValue = $this->superCache->increment('counter', 5); + $this->assertEquals(5, $newValue); + + // Incrementa nuovamente la chiave esistente + $newValue = $this->superCache->increment('counter', 3); + $this->assertEquals(8, $newValue); + } + + public function testDecrement(): void + { + // Decrementa una chiave che non esiste (crea con valore -3) + $newValue = $this->superCache->decrement('counter', 3); + $this->assertEquals(-3, $newValue); + + // Decrementa nuovamente la chiave esistente + $newValue = $this->superCache->decrement('counter', 2); + $this->assertEquals(-5, $newValue); + } + + public function testGetKeys(): void + { + $this->superCache->put('product:1', 'product_value_1'); + $this->superCache->put('product:2', 'product_value_2'); + $this->superCache->put('order:1', 'order_value_1'); + + $keys = $this->superCache->getKeys(['product:*']); + + $this->assertCount(2, $keys); + $this->assertArrayHasKey('supercache:product:1', $keys); + $this->assertArrayHasKey('supercache:product:2', $keys); + $this->assertArrayNotHasKey('supercache:order:1', $keys); + } +}