Full Code of kirkbushell/eloquence for AI

master 2da9afe047a5 cached
69 files
80.6 KB
21.6k tokens
229 symbols
1 requests
Download .txt
Repository: kirkbushell/eloquence
Branch: master
Commit: 2da9afe047a5
Files: 69
Total size: 80.6 KB

Directory structure:
gitextract_qf5a7nt9/

├── .github/
│   └── workflows/
│       └── test.yml
├── .gitignore
├── LICENSE
├── README.md
├── composer.json
├── config/
│   └── eloquence.php
├── phpunit.xml
├── src/
│   ├── Behaviours/
│   │   ├── CacheConfig.php
│   │   ├── Cacheable.php
│   │   ├── CountCache/
│   │   │   ├── CountCache.php
│   │   │   ├── CountedBy.php
│   │   │   ├── HasCounts.php
│   │   │   └── Observer.php
│   │   ├── HasCamelCasing.php
│   │   ├── HasSlugs.php
│   │   ├── ReadOnly/
│   │   │   ├── HasReadOnly.php
│   │   │   └── WriteAccessDenied.php
│   │   ├── Slug.php
│   │   ├── SumCache/
│   │   │   ├── HasSums.php
│   │   │   ├── Observer.php
│   │   │   ├── SumCache.php
│   │   │   ├── Summable.php
│   │   │   └── SummedBy.php
│   │   └── ValueCache/
│   │       ├── HasValues.php
│   │       ├── Observer.php
│   │       ├── ValueCache.php
│   │       └── ValuedBy.php
│   ├── Database/
│   │   └── Model.php
│   ├── EloquenceServiceProvider.php
│   ├── Exceptions/
│   │   └── UnableToCreateSlugException.php
│   └── Utilities/
│       ├── DBQueryLog.php
│       └── RebuildCaches.php
└── tests/
    ├── Acceptance/
    │   ├── AcceptanceTestCase.php
    │   ├── ChainedAggregatesTest.php
    │   ├── CountCacheTest.php
    │   ├── GuardedColumnsTest.php
    │   ├── HasSlugsTest.php
    │   ├── Models/
    │   │   ├── Category.php
    │   │   ├── CategoryFactory.php
    │   │   ├── Comment.php
    │   │   ├── CommentFactory.php
    │   │   ├── GuardedUser.php
    │   │   ├── Item.php
    │   │   ├── ItemFactory.php
    │   │   ├── Order.php
    │   │   ├── OrderFactory.php
    │   │   ├── Post.php
    │   │   ├── PostFactory.php
    │   │   ├── Role.php
    │   │   ├── User.php
    │   │   └── UserFactory.php
    │   ├── RebuildCacheTest.php
    │   ├── RebuildCachesCommandTest.php
    │   ├── SumCacheTest.php
    │   └── ValueCacheTest.php
    └── Unit/
        ├── Behaviours/
        │   ├── ReadOnly/
        │   │   └── HasReadOnlyTest.php
        │   └── SlugTest.php
        ├── Database/
        │   └── Traits/
        │       └── HasCamelCasingTest.php
        ├── Stubs/
        │   ├── CountCache/
        │   │   ├── Comment.php
        │   │   ├── Post.php
        │   │   └── User.php
        │   ├── ModelStub.php
        │   ├── ParentModelStub.php
        │   ├── PivotModelStub.php
        │   ├── ReadOnlyModelStub.php
        │   ├── RealModelStub.php
        │   └── SumCache/
        │       ├── Item.php
        │       └── Order.php
        └── TestCase.php

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/test.yml
================================================
name: Test

on: ['push', 'pull_request']

jobs:
  ci:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest]
        php: ['8.1', '8.2']

    name: PHP ${{ matrix.php }} - ${{ matrix.os }} - ${{ matrix.dependency-version }}

    steps:
    - name: Checkout
      uses: actions/checkout@v2

    - name: Setup PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: ${{ matrix.php }}
        tools: composer:v2
        coverage: none

    - name: Install PHP dependencies
      run: composer update --prefer-stable --no-interaction --no-progress

    - name: Unit Tests
      run: composer test

================================================
FILE: .gitignore
================================================
/vendor
composer.phar
composer.lock
.phpunit.result.cache
.DS_Store
.idea*
.php-cs-fixer.cache


================================================
FILE: LICENSE
================================================
Copyright (c) 2014-2015 Kirk Bushell

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

================================================
FILE: README.md
================================================
# Eloquence

![Version](https://img.shields.io/packagist/v/kirkbushell/eloquence.svg)
![Downloads](https://img.shields.io/packagist/dt/kirkbushell/eloquence.svg)
[![Test](https://github.com/kirkbushell/eloquence/actions/workflows/test.yml/badge.svg)](https://github.com/kirkbushell/eloquence/actions/workflows/test.yml)

Eloquence is a package to extend Laravel's base Eloquent models and functionality.

It provides a number of utilities and attributes to work with Eloquent in new and useful ways,
such as camel cased attributes (such as for JSON apis and code style cohesion), data aggregation and more.

## Installation

Install the package via composer:

    composer require kirkbushell/eloquence

## Usage

Eloquence is automatically discoverable by Laravel, and shouldn't require any further steps. For those on earlier 
versions of Laravel, you can add the package as per normal in your config/app.php file:

    'Eloquence\EloquenceServiceProvider',

The service provider doesn't do much, other than enable the query log, if configured.

## Readonly models

Eloquence supports the protection of models by ensuring that they can only be loaded from the database, and not
written to, or have their values changed. This is useful for data you do not wish to be altered, or in cases
where you may be sharing models across domain boundaries.

To use, simply add the HasReadOnly trait to your model:

```php
use \Eloquence\Behaviours\Readonly\HasReadOnly;

class Log extends Model {
    use HasReadOnly;
}
```

## Camel case all the things!

For those of us who prefer to work with a single coding style right across our applications, using the CamelCased trait 
will ensure you can do exactly that. It transforms all attribute access from camelCase to snake_case in real-time,
providing a unified coding style across your application. This means everything from attribute access to JSON API 
responses will all be camelCased. To use, simply add the CamelCased trait to your model:

    use \Eloquence\Behaviours\HasCamelCasing;

### Note!

Eloquence ***DOES NOT CHANGE*** how you write your schema migrations. You should still be using snake_case when setting 
up your columns and tables in your database schema migrations. This is a good thing - snake_case of columns names is the 
defacto standard within the Laravel community and is widely-used across database schemas, as well.

## Behaviours

Eloquence comes with a system for setting up behaviours, which are really just small libraries that you can use with your 
Eloquent models. The first of these is the count cache.

### Count cache

Count caching is where you cache the result of a count on a related model's record. A simple example of this is where you 
have posts that belong to authors. In this situation, you may want to count the number of posts an author has regularly,
and perhaps even order by this count. In SQL, ordering by an aggregated value is unable to be indexed and therefore - slow.
You can get around this by caching the count of the posts the author has created on the author's model record.

To get this working, you need to do two steps:

1. Use the HasCounts trait on the child model (in this, case Post) and
2. Configure the count cache settings by using the CountedBy attribute.

#### Configuring a count cache

To setup a count cache configuration, we add the HasCounts trait, and setup the CountedBy attribute:

```php
use Eloquence\Behaviours\CountCache\CountedBy;
use Eloquence\Behaviours\CountCache\HasCounts;
use Illuminate\Database\Eloquent\Model;

class Post extends Model {
    use HasCounts;

    #[CountedBy]
    public function author(): BelongsTo
    {
        return $this->belongsTo(Author::class);
    }
}
```

This tells the count cache behaviour that the model has an aggregate count cache on the Author model. So, whenever a post 
is added, modified or deleted, the count cache behaviour will update the appropriate author's count cache for their 
posts. In this case, it would update `post_count` field on the author model.

The example above uses the following standard conventions:

* `post_count` is a defined field on the User model table

It uses your own relationship to find the related record, so no other configuration is required!

Of course, if you have a different setup, or different field names, you can alter the count cache behaviour by defining
the appropriate field to update:

```php
class Post extends Model {
    use HasCounts;

    #[CountedBy(as: 'total_posts')]
    public function author(): BelongsTo
    {
        return $this->belongsTo(Author::class);
    }
}
```

When setting the as: value (using named parameters here from PHP 8.0 for illustrative and readability purposes), you're 
telling the count cache that the aggregate field on the Author model is actually called `total_posts`.

HasCounts is not limited to just one count cache configuration. You can define as many as you need for each BelongsTo
relationship, like so:

```php
#[CountedBy(as: 'total_posts')]
public function author(): BelongsTo
{
    return $this->belongsTo(Author::class);
}

#[CountedBy(as: 'num_posts')]
public function category(): BelongsTo
{
    return $this->belongsTo(Category::class);
}
```

### Sum cache

Sum caching is similar to count caching, except that instead of caching a _count_ of the related model objects, you cache a _sum_
of a particular field on the child model's object. A simple example of this is where you have an order that has many items.
Using sum caching, you can cache the sum of all the items' prices, and store that as a cached sum on the Order model.

To get this working -- just like count caching -- you need to do two steps:

1. Add the HasSums to your child model and
2. Add SummedBy attribute to each relationship method that requires it.

#### Configure the sum cache

To setup the sum cache configuration, simply do the following:

```php
use Eloquence\Behaviours\SumCache\HasSums;
use Eloquence\Behaviours\SumCache\SummedBy;
use Illuminate\Database\Eloquent\Model;

class Item extends Model {
    use HasSums;

    #[SummedBy(from: 'amount', as: 'total_amount')]
    public function order(): BelongsTo
    {
        return $this->belongsTo(Order::class);
    }
}
```

Unlike the count cache which can assume sensible defaults, the sum cache needs a bit more guidance. The example above 
tells the sum cache that there is an `amount` field on Item that needs to be summed to the `total_amount` field on Order.

### Cache recommendations

Because the cache system works directly with other model objects and requires multiple writes to the database, it is
strongly recommended that you wrap your model saves that utilise caches in a transaction. In databases like Postgres,
this is automatic, but for databases like MySQL you need to make sure you're using a transactional database engine
like InnoDB.

The reason for needing transactions is that if any one of your queries fail, your caches will end up out of sync. It's 
better for the entire operation to fail, than to have this happen. Below is an example of using a database transaction
using Laravel's DB facade:

```php
DB::transaction(function() {
    $post = new Post;
    $post->authorId = $author->id;
    $post->save();
});
```

If we return to the example above with posts having authors - if this save was not wrapped in a transaction, and the post
was created but for some reason the database failed immediately after, you would never see the count cache update in the
parent Author model, you'll end up with erroneous data that can be quite difficult to debug.

### Sluggable

Sluggable is another behaviour that allows for the easy addition of model slugs. To use, implement the Sluggable trait:

```php
class User extends Model {
    use HasSlugs;

    public function slugStrategy(): string
    {
        return 'username';
    }
}
```

In the example above, a slug will be created based on the username field of the User model. There are two other
slugs that are supported, as well:

* id and
* uuid

The only difference between the two above, is that if you're using UUIDs, the slug will be generated prior to the model
being saved, based on the uuid field. With ids, which are generally auto-increase strategies - the slug has to be 
generated after the record has been saved - which results in a secondary save call to the database.

That's it! Easy huh?

# Upgrading from v10
Version 11 of Eloquence is a complete rebuild and departure from the original codebase, utilising instead PHP 8.1 attributes
and moving away from traits/class extensions where possible. This means that in some projects many updates will need to 
be made to ensure that your use of Eloquence continues to work.

## 1. Class renames

* Camelcasing has been renamed to HasCamelCasing
* Sluggable renamed to HasSlugs

## 2. Updates to how caches work
All your cache implementations will need to be modified following the guide above. But in short, you'll need to import
and apply the provided attributes to the relationship methods on your models that require aggregated cache values.

The best part about the new architecture with Eloquence, is that you can define your relationships however you want! If 
you have custom where clauses or other conditions that restrict the relationship, Eloquence will respect that. This makes
Eloquence now considerably more powerful and supportive of individual domain requirements than ever before.

Let's use a real case. This is the old approach, using Countable as an example:

```php
class Post extends Model
{
    use Countable;
    
    public function countCaches() {
        return [
            'num_posts' => ['User', 'users_id', 'id']
        ];
    }
}
```

To migrate that to v11, we would do the following:

```php
use Eloquence\Behaviours\CountCache\CountedBy;

class Post extends Model
{
    use \Eloquence\Behaviours\CountCache\HasCounts;
    
    #[CountedBy(as: 'num_posts')]
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
}
```

Note the distinct lack of required configuration. The same applies to the sum behaviour - simply migrate your configuration 
away from the cache functions, and into the attributes above the relationships you wish to have an aggregated cache 
value for.

## Changelog

#### 12.0.1

* Added Readonly model support.

#### 12.0.0

* Added Laravel 12 support

#### 11.0.4

* Bug fix provided by #120 addressing the creation of new models without related model objects

#### 11.0.3

* Bug fix for count cache when relation is removed (#118)
* Identified and applied a similar bugfix for the sum cache

#### 11.0.2

* Fixed a bug where relationships were not being returned

#### 11.0.1

* Fixed dependency error to support Laravel 11

#### 11.0.0

* Complete rework of the Eloquent library - version 11 is **_not_** backwards-compatible
* UUID support removed - both UUIDs and ULIDs are now natively supported in Laravel and have been for some time
* Cache system now works directly with models and their relationships, allowing for fine-grained control over the models it works with
* Console commands removed - model caches can be rebuilt using Model::rebuildCache() if something goes awry
* Fixed a number of bugs across both count and sum caches
* CamelCasing renamed to CamelCased
* Syntax, styling, and standards all modernised

#### 10.0.0

* Boost in version number to match Laravel
* Support for Laravel 10.0+
* Replace date casting with standard Laravel casting (https://laravel.com/docs/10.x/upgrade#model-dates-property)

#### 9.0.0

* Boost in version number to match Laravel
* Support for Laravel 9.0+
* Updated to require PHP 8.1+
* Resolved method deprecation warnings

#### 8.0.0

* Boost in version number to match Laravel
* Support for Laravel 7.3+

* Fixes a bug that resulted with the new guarded attributes logic in eloquent

#### 4.0.1

* Fixes a bug that resulted with the new guarded attributes logic in eloquent

#### 4.0.0

* Laravel 7 support (thanks, @msiemens!)

#### 3.0.0

* Laravel 6 support
* Better slug creation and handling

#### 2.0.7

* Slug uniqueness check upon slug creation for id-based slugs.

#### 2.0.6

* Bug fix when restoring models that was resulting in incorrect count cache values.

#### 2.0.3

* Slugs now implement Jsonable, making them easier to handle in API responses
* New artisan command for rebuilding caches (beta, use at own risk)

#### 2.0.2

* Updated PHP dependency to 5.6+
* CountCache and SumCache behaviours now supported via a service layer

#### 2.0.0

* Sum cache model behaviour added
* Booting of behaviours now done via Laravel trait booting
* Simplification of all behaviours and their uses
* Updated readme/configuration guide

#### 1.4.0

* Slugs when retrieved from a model now return Slug value objects.

#### 1.3.4

* More random, less predictable slugs for id strategies

#### 1.3.3

* Fixed a bug with relationships not being accessible via model properties

#### 1.3.2

* Slugged behaviour
* Fix for fillable attributes

#### 1.3.1

* Relationship fixes
* Fillable attributes bug fix
* Count cache update for changing relationships fix
* Small update for implementing count cache observer

#### 1.3.0

* Count cache model behaviour added
* Many-many relationship casing fix
* Fixed an issue when using ::create

#### 1.2.0

* Laravel 5 support
* Readme updates

#### 1.1.5

* UUID model trait now supports custom UUIDs (instead of only generating them for you)

#### 1.1.4

* UUID fix

#### 1.1.3

* Removed the schema binding on the service provider

#### 1.1.2

* Removed the uuid column creation via custom blueprint

#### 1.1.1

* Dependency bug fix

#### 1.1.0

* UUIDModel trait added
* CamelCaseModel trait added
* Model class updated to use CamelCaseModel trait - deprecated, backwards-compatibility support only
* Eloquence now its own namespace (breaking change)
* EloquenceServiceProvider added use this if you want to overload the base model automatically (required for pivot model camel casing).

#### 1.0.2

* Relationships now support camelCasing for retrieval (thanks @linxgws)

#### 1.0.1

* Fixed an issue with dependency resolution

#### 1.0.0

* Initial implementation
* Camel casing of model attributes now available for both setters and getters

## License

The Laravel framework is open-sourced software licensed under the MIT license.

================================================
FILE: composer.json
================================================
{
    "name": "kirkbushell/eloquence",
    "description": "A set of extensions adding additional functionality and consistency to Laravel's awesome Eloquent library.",
    "keywords": [
        "aggregates",
        "cache",
        "camelcase",
        "camel",
        "case",
        "count",
        "eloquent",
        "laravel",
        "snake_case",
        "snake",
        "sum"
    ],
    "authors": [
        {
            "name": "Kirk Bushell",
            "email": "torm3nt@gmail.com"
        }
    ],
    "require": {
        "php": "^8.1",
        "hashids/hashids": "^4.1||^5.0",
        "illuminate/database": "^10.0||^11.0||^12.0",
        "illuminate/support": "^10.0||^11.0||^12.0",
        "hanneskod/classtools": "^0.1.0",
        "symfony/finder": "^6.3||^7.0"
    },
    "require-dev": {
        "illuminate/events": "^10.0||^12.0",
        "mockery/mockery": "^1.4.4",
        "orchestra/testbench": "^8.0||^10.0",
        "phpunit/phpunit": "^9.5.10||^11.5.3",
        "friendsofphp/php-cs-fixer": "^3.48"
    },
    "autoload": {
        "psr-4": {
            "Eloquence\\": "src/",
            "Tests\\": "tests/"
        }
    },
    "scripts": {
        "test": "phpunit --colors=always",
        "post-autoload-dump": [
            "@clear",
            "@prepare"
        ],
        "clear": "@php vendor/bin/testbench package:purge-skeleton --ansi",
        "prepare": "@php vendor/bin/testbench package:discover --ansi",
        "build": "@php vendor/bin/testbench workbench:build --ansi",
        "serve": [
            "@build",
            "@php vendor/bin/testbench serve"
        ]
    },
    "minimum-stability": "dev",
    "prefer-stable": true,
    "extra": {
        "laravel": {
            "providers": [
                "Eloquence\\EloquenceServiceProvider"
            ]
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Workbench\\App\\": "workbench/app/",
            "Workbench\\Database\\Factories\\": "workbench/database/factories/",
            "Workbench\\Database\\Seeders\\": "workbench/database/seeders/"
        }
    },
    "config": {
        "allow-plugins": {
            "bamarni/composer-bin-plugin": true
        }
    }
}


================================================
FILE: config/eloquence.php
================================================
<?php

return [
    'logging' => [
        'enabled' => env('ELOQUENCE_LOGGING_ENABLED', false),
        'driver' => env('ELOQUENCE_LOGGING_DRIVER', env('LOG_CHANNEL', 'stack')),
    ]
];


================================================
FILE: phpunit.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="vendor/autoload.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false"
>
    <testsuites>
        <testsuite name="unit">
            <directory suffix="Test.php">./tests/Unit</directory>
        </testsuite>
        <testsuite name="acceptance">
            <directory suffix="Test.php">./tests/Acceptance</directory>
        </testsuite>
    </testsuites>
    <php>
        <env name="API_KEY" value="fakeApiKey" force="true" />
    </php>
</phpunit>


================================================
FILE: src/Behaviours/CacheConfig.php
================================================
<?php

namespace Eloquence\Behaviours;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Relation;

class CacheConfig
{
    public function __construct(readonly string $relationName, readonly string $aggregateField, readonly string $sourceField = 'id')
    {
    }

    /**
     * Returns the actual Relation object - such as BelongsTo. This method makes a call to the relationship
     * method specified on the model object, and is used to infer data about the relationship.
     */
    public function relation(Model $model): Relation
    {
        return $model->{$this->relationName}();
    }

    /**
     * Returns the current related model.
     */
    public function relatedModel(Model $model): ?Model
    {
        return $model->{$this->relationName};
    }

    /**
     * Returns -a- related model object - this object is actually empty, and is found on the query builder, used to
     * infer certain information abut the relationship that cannot be found on CacheConfig::relation.
     */
    public function emptyRelatedModel(Model $model): Model
    {
        return $this->relation($model)->getModel();
    }

    /**
     * Returns the related model class name.
     */
    public function relatedModelClass($model): string
    {
        return get_class($this->emptyRelatedModel($model));
    }

    public function foreignKeyName(Model $model): string
    {
        return $this->relation($model)->getForeignKeyName();
    }
}


================================================
FILE: src/Behaviours/Cacheable.php
================================================
<?php

namespace Eloquence\Behaviours;

use Closure;
use Eloquence\Behaviours\SumCache\SummedBy;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use ReflectionClass;
use ReflectionMethod;
use Tests\Acceptance\Models\User;

/**
 * The cacheable trait is concerned with the related models.
 */
trait Cacheable
{
    /**
     * Allows cacheable to work with implementors and their unique relationship methods.
     *
     * @return array
     */
    abstract private function configuration(): array;

    /**
     * Template method for setting up the CacheConfig object.
     */
    protected function config($relationName, $aggregateField): CacheConfig
    {
        return new CacheConfig($relationName, $aggregateField);
    }

    /**
     * Helper method for easier use of the implementing classes.
     */
    public static function for(Model $model): self
    {
        return new self($model);
    }

    public function reflect(string $attributeClass, \Closure $fn)
    {
        $reflect = new ReflectionClass($this->model);

        // This behemoth cycles through all valid methods, and then gets only the attributes we care about,
        // formatting it in a way that is usable by our various aggregate service classes.
        return collect($reflect->getMethods())
            ->filter(fn (ReflectionMethod $method) => count($method->getAttributes($attributeClass)) > 0)
            ->flatten()
            ->map(function (ReflectionMethod $method) use ($attributeClass) {
                return collect($method->getAttributes($attributeClass))->map(fn (\ReflectionAttribute $attribute) => [
                    'name' => $method->name,
                    'attribute' => $attribute->newInstance(),
                ])->toArray();
            })
            ->flatten(1)
            ->mapWithKeys($fn)
            ->toArray();
    }

    /**
     * Applies the provided function using the relevant configuration to all configured relations. Configuration
     * would be one of countedBy, summedBy, averagedBy.etc.
     */
    protected function apply(Closure $function): void
    {
        foreach ($this->configuration() as $key => $value) {
            $function($this->config($key, $value));
        }
    }

    /**
     * Updates a table's record based on the query information provided in the $config variable.
     *
     * @param string $operation Whether to increase or decrease a value. Valid values: +/-
     */
    protected function updateCacheRecord(Model $model, CacheConfig $config, string $operation, int $amount): void
    {
        $this->updateCacheValue($model, $config, $amount);
    }

    /**
     * It's a bit hard to read what's going on in this method, so let's elaborate.
     *
     * 1. Get the foreign key of the model that needs to be queried.
     * 2. Get the aggregate value for all records with that foreign key.
     * 3. Update the related model wth the relevant aggregate value.
     */
    public function rebuildCacheRecord(CacheConfig $config, Model $model, $command): void
    {
        $foreignKey = $config->foreignKeyName($model);
        $related = $config->emptyRelatedModel($model);

        $updateSql = sprintf(
            'UPDATE %s SET %s = COALESCE((SELECT %s(%s) FROM %s WHERE %s = %s.%s), 0)',
            $related->getTable(),
            $config->aggregateField,
            $command,
            $config->sourceField,
            $model->getTable(),
            $foreignKey,
            $related->getTable(),
            $related->getKeyName()
        );

        DB::update($updateSql);
    }

    /**
     * Update the cache value for the model.
     */
    protected function updateCacheValue(?Model $model, CacheConfig $config, $value): void
    {
        if(!$model){
            return;
        }

        $model->{$config->aggregateField} = $model->{$config->aggregateField} + $value;
        $model->save();
    }
}


================================================
FILE: src/Behaviours/CountCache/CountCache.php
================================================
<?php

namespace Eloquence\Behaviours\CountCache;

use Eloquence\Behaviours\Cacheable;
use Eloquence\Behaviours\CacheConfig;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;

/**
 * The count cache does operations on the model that has just updated, works out the related models, and calls the appropriate operations on cacheable.
 */
class CountCache
{
    use Cacheable;

    private function __construct(private Model $model)
    {
    }

    private function configuration(): array
    {
        return $this->reflect(CountedBy::class, function (array $config) {
            $aggregateField = $config['attribute']->as ?? Str::lower(Str::snake(class_basename($this->model))).'_count';
            return [$config['name'] => $aggregateField];
        });
    }

    /**
     * When a model is updated, its foreign keys may have changed. In this situation, we need to update both the original
     * related model, and the new one.The original would be deducted the value, whilst the new one is increased.
     */
    public function update(): void
    {
        $this->apply(function (CacheConfig $config) {
            $foreignKey = $config->foreignKeyName($this->model);

            // We only do updates if the foreign key was actually changed
            if (!$this->model->wasChanged($foreignKey)) {
                return;
            }

            // for the minus operation, we first have to get the model that is no longer associated with this one.
            $originalRelatedModel = $config->emptyRelatedModel($this->model)->find($this->model->getOriginal($foreignKey));

            $this->updateCacheValue($originalRelatedModel, $config, -1);

            // If there is no longer a relation, nothing more to do.
            if (null === $this->model->{$foreignKey}) return;

            $this->updateCacheValue($config->relatedModel($this->model), $config, 1);
        });
    }

    /**
     * Rebuild the count caches from the database for each matching model.
     */
    public function rebuild(): void
    {
        $this->apply(function (CacheConfig $config) {
            $this->rebuildCacheRecord($config, $this->model, 'count');
        });
    }

    public function increment(): void
    {
        $this->apply(function (CacheConfig $config) {
            $this->updateCacheValue($config->relatedModel($this->model), $config, 1);
        });
    }

    public function decrement(): void
    {
        $this->apply(function (CacheConfig $config) {
            $this->updateCacheValue($config->relatedModel($this->model), $config, -1);
        });
    }
}

================================================
FILE: src/Behaviours/CountCache/CountedBy.php
================================================
<?php

namespace Eloquence\Behaviours\CountCache;

use Attribute;

#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
class CountedBy
{
    public function __construct(readonly ?string $as = null)
    {
    }
}


================================================
FILE: src/Behaviours/CountCache/HasCounts.php
================================================
<?php

namespace Eloquence\Behaviours\CountCache;

trait HasCounts
{
    public static function bootHasCounts(): void
    {
        static::observe(Observer::class);
    }

    public static function rebuildCountCache(): void
    {
        CountCache::for(new self())->rebuild();
    }
}


================================================
FILE: src/Behaviours/CountCache/Observer.php
================================================
<?php

namespace Eloquence\Behaviours\CountCache;

/**
 * The Observer is used for watching for model updates and making the appropriate changes
 * as required. This includes watching for created, deleted, updated and restored events.
 */
class Observer
{
    /**
     * When the model has been created, increment the count cache by 1.
     *
     * @param $model
     */
    public function created($model): void
    {
        CountCache::for($model)->increment();
    }

    /**
     * When the model is deleted, decrement the count cache by 1.
     *
     * @param $model
     */
    public function deleted($model): void
    {
        CountCache::for($model)->decrement();
    }

    /**
     * When the model is updated, update the count cache.
     *
     * @param $model
     */
    public function updated($model): void
    {
        CountCache::for($model)->update();
    }

    /**
     * When the model is restored, again increment the count cache by 1.
     *
     * @param $model
     */
    public function restored($model): void
    {
        CountCache::for($model)->increment();
    }
}


================================================
FILE: src/Behaviours/HasCamelCasing.php
================================================
<?php

namespace Eloquence\Behaviours;

use Illuminate\Support\Str;

trait HasCamelCasing
{
    /**
     * Alter eloquent model behaviour so that model attributes can be accessed via camelCase, but more importantly,
     * attributes also get returned as camelCase fields.
     *
     * @var bool
     */
    public $enforceCamelCase = true;

    /**
     * Overloads the eloquent isGuardableColumn method to ensure that we are checking for the existence of
     * the snake_cased column name.
     *
     * @param  string  $key
     * @return bool
     */
    protected function isGuardableColumn($key)
    {
        return parent::isGuardableColumn($this->getSnakeKey($key));
    }

    /**
     * Overloads the eloquent setAttribute method to ensure that fields accessed
     * in any case are converted to snake_case, which is the defacto standard
     * for field names in databases.
     *
     * @param string $key
     * @param mixed  $value
     * @return mixed
     */
    public function setAttribute($key, $value)
    {
        return parent::setAttribute($this->getSnakeKey($key), $value);
    }

    /**
     * Retrieve a given attribute but allow it to be accessed via alternative case methods (such as camelCase).
     *
     * @param  string $key
     * @return mixed
     */
    public function getAttribute($key): mixed
    {
        return $this->isRelation($key) ? parent::getAttribute($key) : parent::getAttribute($this->getSnakeKey($key));
    }

    /**
     * Return the attributes for the model, converting field casing if necessary.
     *
     * @return array
     */
    public function attributesToArray()
    {
        return $this->toCamelCase(parent::attributesToArray());
    }

    /**
     * Get the model's relationships, converting field casing if necessary.
     *
     * @return array
     */
    public function relationsToArray()
    {
        return $this->toCamelCase(parent::relationsToArray());
    }

    /**
     * Overloads eloquent's getHidden method to ensure that hidden fields declared
     * in camelCase are actually hidden and not exposed when models are turned
     * into arrays.
     *
     * @return array
     */
    public function getHidden()
    {
        return array_map(Str::class.'::snake', $this->hidden);
    }

    /**
     * Overloads the eloquent getCasts method to ensure that cast field declarations
     * can be made in camelCase but mapped to/from DB in snake_case.
     *
     * @return array
     */
    public function getCasts()
    {
        return collect(parent::getCasts())
            ->mapWithKeys(function ($cast, $key) {
                return [Str::snake($key) => $cast];
            })
            ->toArray();
    }

    /**
     * Converts a given array of attribute keys to the casing required by CamelCased.
     *
     * @param mixed $attributes
     * @return array
     */
    public function toCamelCase($attributes)
    {
        $convertedAttributes = [];

        foreach ($attributes as $key => $value) {
            $key = $this->getTrueKey($key);
            $convertedAttributes[$key] = $value;
        }

        return $convertedAttributes;
    }

    /**
     * Converts a given array of attribute keys to the casing required by CamelCased.
     *
     * @param $attributes
     * @return array
     */
    public function toSnakeCase($attributes)
    {
        $convertedAttributes = [];

        foreach ($attributes as $key => $value) {
            $convertedAttributes[$this->getSnakeKey($key)] = $value;
        }

        return $convertedAttributes;
    }

    /**
     * Retrieves the true key name for a key.
     *
     * @param $key
     * @return string
     */
    public function getTrueKey($key)
    {
        // If the key is a pivot key, leave it alone - this is required internal behaviour
        // of Eloquent for dealing with many:many relationships.
        if ($this->isCamelCase() && strpos($key, 'pivot_') === false) {
            $key = Str::camel($key);
        }

        return $key;
    }

    /**
     * Determines whether the model (or its parent) requires camelcasing. This is required
     * for pivot models whereby they actually depend on their parents for this feature.
     *
     * @return bool
     */
    public function isCamelCase()
    {
        return $this->enforceCamelCase or (isset($this->parent) && method_exists($this->parent, 'isCamelCase') && $this->parent->isCamelCase());
    }

    /**
     * If the field names need to be converted so that they can be accessed by camelCase, then we can do that here.
     *
     * @param $key
     * @return string
     */
    protected function getSnakeKey($key)
    {
        return Str::snake($key);
    }

    /**
     * Because we are changing the case of keys and want to use camelCase throughout the application, whenever
     * we do isset checks we need to ensure that we check using snake_case.
     *
     * @param $key
     * @return mixed
     */
    public function __isset($key)
    {
        return parent::__isset($key) || parent::__isset($this->getSnakeKey($key));
    }

    /**
     * Because we are changing the case of keys and want to use camelCase throughout the application, whenever
     * we do unset variables we need to ensure that we unset using snake_case.
     *
     * @param $key
     * @return void
     */
    public function __unset($key)
    {
        return parent::__unset($this->getSnakeKey($key));
    }
}

================================================
FILE: src/Behaviours/HasSlugs.php
================================================
<?php

namespace Eloquence\Behaviours;

use Eloquence\Exceptions\UnableToCreateSlugException;
use Illuminate\Support\Str;

trait HasSlugs
{
    /**
     * When added to a model, the trait will bind to the creating and created
     * events, generating the appropriate slugs as necessary.
     */
    public static function bootHasSlugs(): void
    {
        static::creating(function ($model) {
            $model->generateSlug();
        });
    }

    /**
     * Generate a slug based on the main model key.
     */
    public function generateIdSlug(): void
    {
        $slug = Slug::fromId($this->getKey() ?? rand());

        // Ensure slug is unique (since the fromId() algorithm doesn't produce unique slugs)
        $attempts = 10;
        while ($this->slugExists($slug)) {
            if ($attempts <= 0) {
                throw new UnableToCreateSlugException(
                    "Unable to find unique slug for record '{$this->getKey()}', tried 10 times..."
                );
            }

            $slug = Slug::random();
            $attempts--;
        }

        $this->setSlugValue($slug);
    }

    /**
     * Generate a slug string based on the fields required.
     */
    public function generateTitleSlug(array $fields): void
    {
        static $attempts = 0;

        $titleSlug = Slug::fromTitle(implode('-', $this->getTitleFields($fields)));

        // This is not the first time we've attempted to create a title slug, so - let's make it more unique
        if ($attempts > 0) {
            $titleSlug . "-{$attempts}";
        }

        $this->setSlugValue($titleSlug);

        $attempts++;
    }

    /**
     * Because a title slug can be created from multiple sources (such as an article title, a category title.etc.),
     * this allows us to search out those fields from related objects and return the combined values.
     */
    public function getTitleFields(array $fields): array
    {
        return array_map(function ($field) {
            if (Str::contains($field, '.')) {
                return object_get($this, $field); // this acts as a delimiter, which we can replace with /
            } else {
                return $this->{$field};
            }
        }, $fields);
    }

    /**
     * Generate the slug for the model based on the model's slug strategy.
     */
    public function generateSlug(): void
    {
        $strategy = $this->slugStrategy();

        if (in_array($strategy, ['uuid', 'id'])) {
            $this->generateIdSlug();
        } elseif ($strategy != 'id') {
            $this->generateTitleSlug((array) $strategy);
        }
    }

    public function setSlugValue(Slug $value): void
    {
        $this->{$this->slugField()} = $value;
    }

    /**
     * Allows laravel to start using the slug field as the string for routes.
     */
    public function getRouteKey(): mixed
    {
        $slug = $this->slugField();

        return $this->$slug;
    }

    /**
     * Return the name of the field you wish to use for the slug.
     */
    protected function slugField(): string
    {
        return 'slug';
    }

    /**
     * Return the strategy to use for the slug.
     *
     * When using id or uuid, simply return 'id' or 'uuid' from the method below. However,
     * for creating a title-based slug - simply return the field you want it to be based on
     *
     * Eg:
     *
     *  return 'id';
     *  return 'uuid';
     *  return 'name';
     *
     * If you'd like your slug to be based on more than one field, return it in dot-notation:
     *
     *  return 'first_name.last_name';
     *
     * If you're using the camelcase model trait, then you can use that format:
     *
     *  return 'firstName.lastName';
     *
     * @return string
     */
    public function slugStrategy(): string
    {
        return 'id';
    }

    private function slugExists(Slug $slug): bool
    {
        return $this->newQuery()
            ->where($this->slugField(), (string) $slug)
            ->where($this->getQualifiedKeyName(), '!=', $this->getKey())
            ->exists();
    }
}


================================================
FILE: src/Behaviours/ReadOnly/HasReadOnly.php
================================================
<?php

namespace Eloquence\Behaviours\ReadOnly;

trait HasReadOnly
{
    public function setAttribute($key, $value)
    {
        throw new WriteAccessDenied(get_class($this));
    }

    public function save(array $options = [])
    {
        throw new WriteAccessDenied(get_class($this));
    }
}

================================================
FILE: src/Behaviours/ReadOnly/WriteAccessDenied.php
================================================
<?php

namespace Eloquence\Behaviours\ReadOnly;

final class WriteAccessDenied extends \RuntimeException
{
    public function __construct(string $model)
    {
        $this->message = "Write access denied for model {$model}.";
    }
}

================================================
FILE: src/Behaviours/Slug.php
================================================
<?php

namespace Eloquence\Behaviours;

use Hashids\Hashids;
use Illuminate\Contracts\Database\Eloquent\Castable;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Contracts\Support\Jsonable;
use Illuminate\Support\Str;

class Slug implements Castable, Jsonable
{
    /**
     * @var string
     */
    private $slug;

    /**
     * Creates a new instance of the Slug class based on the slug string provided.
     *
     * @param string $slug
     * @throws \Exception
     */
    public function __construct($slug)
    {
        $this->slug = $slug;
    }

    /**
     * Generate a new 8-character slug.
     *
     * @param integer $id
     * @return Slug
     */
    public static function fromId($id)
    {
        $salt = md5(uniqid().$id);
        $alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $slug = with(new Hashids($salt, 8, $alphabet))->encode($id);

        return new Slug($slug);
    }

    /**
     * Generate a new entirely random 8-character slug
     */
    public static function random(): Slug
    {
        $exclude = ['/', '+', '=', 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
        $length = 8;
        $string = '';

        while (($len = strlen($string)) < $length) {
            $size = $length - $len;
            $bytes = random_bytes($size);
            $string .= substr(str_replace($exclude, '', base64_encode($bytes)), 0, $size);
        }

        return new Slug($string);
    }

    public static function fromTitle($title): Slug
    {
        return new Slug(Str::slug($title));
    }

    public function __toString(): string
    {
        return $this->slug;
    }

    public function toJson($options = 0): string
    {
        return $this->__toString();
    }

    public static function castUsing(array $arguments): CastsAttributes
    {
        return new class implements CastsAttributes
        {
            public function get($model, string $key, $value, array $attributes): ?Slug
            {
                return null === $value ? $value : new Slug($value);
            }

            public function set($model, string $key, $value, array $attributes): array
            {
                return [
                    $key => null === $value ? $value : (string) $value
                ];
            }
        };
    }
}

================================================
FILE: src/Behaviours/SumCache/HasSums.php
================================================
<?php

namespace Eloquence\Behaviours\SumCache;

trait HasSums
{
    public static function bootHasSums(): void
    {
        static::observe(Observer::class);
    }

    public static function rebuildSumCache(): void
    {
        SumCache::for(new self())->rebuild();
    }
}


================================================
FILE: src/Behaviours/SumCache/Observer.php
================================================
<?php

namespace Eloquence\Behaviours\SumCache;

class Observer
{
    public function created($model)
    {
        SumCache::for($model)->increase();
    }

    public function updated($model)
    {
        SumCache::for($model)->update();
    }

    public function deleted($model)
    {
        SumCache::for($model)->decrease();
    }

    public function restored($model)
    {
        SumCache::for($model)->increase();
    }
}


================================================
FILE: src/Behaviours/SumCache/SumCache.php
================================================
<?php

namespace Eloquence\Behaviours\SumCache;

use Eloquence\Behaviours\Cacheable;
use Eloquence\Behaviours\CacheConfig;
use Illuminate\Database\Eloquent\Model;

class SumCache
{
    use Cacheable;

    private function __construct(private Model $model)
    {
    }

    private function configuration(): array
    {
        return $this->reflect(SummedBy::class, function (array $config) {
            return [$config['name'] => [$config['attribute']->as => $config['attribute']->from]];
        });
    }

    /**
     * Rebuild the count caches from the database
     */
    public function rebuild(): void
    {
        $this->apply(function ($config) {
            $this->rebuildCacheRecord($config, $this->model, 'sum');
        });
    }

    public function increase(): void
    {
        $this->apply(function (CacheConfig $config) {
            $this->updateCacheValue($config->relatedModel($this->model), $config, (int) $this->model->{$config->sourceField});
        });
    }

    public function decrease(): void
    {
        $this->apply(function (CacheConfig $config) {
            $this->updateCacheValue($config->relatedModel($this->model), $config, -(int) $this->model->{$config->sourceField});
        });
    }

    /**
     * Update the cache for all operations.
     */
    public function update(): void
    {
        $this->apply(function (CacheConfig $config) {
            $foreignKey = $config->foreignKeyName($this->model);

            if ($this->model->wasChanged($foreignKey)) {
                // for the minus operation, we first have to get the model that is no longer associated with this one.
                $originalRelatedModel = $config->emptyRelatedModel($this->model)->find($this->model->getOriginal($foreignKey));
                $this->updateCacheValue($originalRelatedModel, $config, -$this->model->getOriginal($config->sourceField));

                if (null === $this->model->{$foreignKey}) return;

                $this->updateCacheValue($config->relatedModel($this->model), $config, $this->model->{$config->sourceField});
            } else {
                $difference = $this->model->{$config->sourceField} - $this->model->getOriginal($config->sourceField);
                $this->updateCacheValue($config->relatedModel($this->model), $config, $difference);
            }
        });
    }

    /**
     * Takes a registered sum cache, and setups up defaults.
     */
    protected function config($relationName, $sourceField): CacheConfig
    {
        $keys = array_keys($sourceField);

        $aggregateField = $keys[0];
        $sourceField = $sourceField[$aggregateField];

        return new CacheConfig($relationName, $aggregateField, $sourceField);
    }
}

================================================
FILE: src/Behaviours/SumCache/Summable.php
================================================
<?php

namespace Eloquence\Behaviours\SumCache;

interface Summable
{
    /**
     * Returns a key->value array of the relationship you want to utilise to update the sum, followed
     * by the source field you wish to sum. For example, if you have an order model that has many items
     * and you wish to sum the item amount, you can return the following:
     *
     * ['order' => 'amount']
     *
     * Of course, if you want to customise the field saving the total as well, you can do that too:
     *
     * ['relationship' => ['aggregate_field' => 'source_field']]
     *
     * In real-world terms:
     *
     * ['order' => ['total_amount' => 'amount']]
     *
     * By default, the sum cache will take the source field, and add "_total" to it on the related model.
     *
     * @return array
     */
    public function summedBy(): array;
}


================================================
FILE: src/Behaviours/SumCache/SummedBy.php
================================================
<?php

namespace Eloquence\Behaviours\SumCache;

use Attribute;

#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
class SummedBy
{
    public function __construct(readonly string $from, readonly string $as)
    {
    }
}


================================================
FILE: src/Behaviours/ValueCache/HasValues.php
================================================
<?php

namespace Eloquence\Behaviours\ValueCache;

trait HasValues
{
    public static function bootHasValues(): void
    {
        static::observe(Observer::class);
    }

    public static function rebuildValueCache(): void
    {
        ValueCache::for(new self())->rebuild();
    }
}

================================================
FILE: src/Behaviours/ValueCache/Observer.php
================================================
<?php

namespace Eloquence\Behaviours\ValueCache;

class Observer
{
    public function created($model): void
    {
        ValueCache::for($model)->updateRelated(true);
    }

    public function updated($model): void
    {
        ValueCache::for($model)->updateRelated(false);
    }
}

================================================
FILE: src/Behaviours/ValueCache/ValueCache.php
================================================
<?php

namespace Eloquence\Behaviours\ValueCache;

use Eloquence\Behaviours\Cacheable;
use Eloquence\Behaviours\CacheConfig;
use Illuminate\Database\Eloquent\Model;

class ValueCache
{
    use Cacheable;

    private function __construct(private Model $model)
    {
    }

    protected function config($relationName, $sourceField): CacheConfig
    {
        $keys = array_keys($sourceField);

        $aggregateField = $keys[0];
        $sourceField = $sourceField[$aggregateField];

        return new CacheConfig($relationName, $aggregateField, $sourceField);
    }

    public function rebuild()
    {

    }

    public function updateRelated(bool $new): void
    {
        $this->apply(function(CacheConfig $config) use ($new) {
            $foreignKey = $config->foreignKeyName($this->model);

            // We only do work if the model previously existed and the source field has changed, or the model was newly created in the database.
            if (!($new || $this->model->wasChanged($config->sourceField))) {
                return;
            }

            if(!$relatedModel = $config->emptyRelatedModel($this->model)->find($this->model->$foreignKey)){
                return;
            }

            $relatedModel->{$config->aggregateField} = $this->model->{$config->sourceField};
            $relatedModel->save();
        });
    }

    private function configuration(): array
    {
        return $this->reflect(ValuedBy::class, function (array $config) {
            return [$config['name'] => [$config['attribute']->as => $config['attribute']->from]];
        });
    }
}


================================================
FILE: src/Behaviours/ValueCache/ValuedBy.php
================================================
<?php

namespace Eloquence\Behaviours\ValueCache;

use Attribute;

#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
class ValuedBy
{
    public function __construct(readonly string $from, readonly ?string $as)
    {
    }
}


================================================
FILE: src/Database/Model.php
================================================
<?php

namespace Eloquence\Database;

use Eloquence\Behaviours\HasCamelCasing;

/**
 * Class Model
 *
 * Have your models extend the model class to include the below traits.
 *
 * @package Eloquence\Database
 */
abstract class Model extends \Illuminate\Database\Eloquent\Model
{
    use HasCamelCasing;
}


================================================
FILE: src/EloquenceServiceProvider.php
================================================
<?php

namespace Eloquence;

use Eloquence\Utilities\DBQueryLog;
use Illuminate\Support\ServiceProvider;

class EloquenceServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        $this->publishes([
            __DIR__.'/../config/eloquence.php' => config_path('eloquence.php'),
        ], 'config');

        $this->initialiseDbQueryLog();
        $this->initialiseCommands();
    }

    protected function initialiseDbQueryLog(): void
    {
        DBQueryLog::initialise();
    }

    private function initialiseCommands(): void
    {
        $this->commands([
            Utilities\RebuildCaches::class,
        ]);
    }
}


================================================
FILE: src/Exceptions/UnableToCreateSlugException.php
================================================
<?php

namespace Eloquence\Exceptions;

class UnableToCreateSlugException extends \Exception
{
}


================================================
FILE: src/Utilities/DBQueryLog.php
================================================
<?php

namespace Eloquence\Utilities;

use Illuminate\Database\Events\QueryExecuted;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class DBQueryLog
{
    /**
     * Enables the db query log. The default logging driver is set to the your logging default. However, if you wish
     * to configure this to use a different driver specifically for query logs (recommended), then you can configure
     * that in the eloquence configuration file.
     *
     * @return void
     */
    public static function initialise(): void
    {
        if (!config('eloquence.logging.enabled')) {
            return;
        }

        DB::listen(function (QueryExecuted $query) {
            Log::driver(config('eloquence.logging.driver'))->debug("[{$query->time}ms] $query->sql", $query->bindings);
        });
    }
}


================================================
FILE: src/Utilities/RebuildCaches.php
================================================
<?php

namespace Eloquence\Utilities;

use Eloquence\Behaviours\CountCache\HasCounts;
use Eloquence\Behaviours\SumCache\HasSums;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;

class RebuildCaches extends Command
{
    protected $signature = 'eloquence:rebuild-caches {path? : The absolute path to search for models. Defaults to your application path.}';
    protected $description = 'Rebuilds the caches of all affected models within the database.';

    private $caches = [
        HasCounts::class => 'rebuildCountCache',
        HasSums::class => 'rebuildSumCache',
    ];

    public function handle(): void
    {
        $path = $this->argument('path') ?? app_path();

        $this->allModelsUsingCaches($path)->each(function (string $class) {
            $traits = class_uses_recursive($class);

            foreach ($this->caches as $trait => $method) {
                if (!in_array($trait, $traits)) {
                    continue;
                }

                $class::$method();
            }
        });
    }

    /**
     * Returns only those models that are utilising eloquence cache mechanisms.
     *
     * @param string $path
     * @return Collection
     */
    private function allModelsUsingCaches(string $path): Collection
    {
        return collect(Finder::create()->files()->in($path)->name('*.php'))
            ->filter(fn (SplFileInfo $file) => $file->getFilename()[0] === Str::upper($file->getFilename()[0]))
            ->map(fn (SplFileInfo $file) => $this->fullyQualifiedClassName($file))
            ->filter(fn (string $class) => is_subclass_of($class, Model::class))
            ->filter(fn (string $class) => $this->usesCaches($class));
    }

    /**
     * Determines the fully qualified class name of the provided file.
     *
     * @param SplFileInfo $file
     * @return string
     */
    private function fullyQualifiedClassName(SplFileInfo $file)
    {
        $tokens = \PhpToken::tokenize($file->getContents());
        $namespace = null;
        $class = null;

        foreach ($tokens as $i => $token) {
            if ($token->is(T_NAMESPACE)) {
                $namespace = $tokens[$i + 2]->text;
            }

            if ($token->is(T_CLASS)) {
                $class = $tokens[$i + 2]->text;
            }

            if ($namespace && $class) {
                break;
            }
        }

        if (!$namespace || !$class) {
            $this->error(sprintf('Could not find namespace or class in %s', $file->getRealPath()));
        }

        return sprintf('%s\\%s', $namespace, $class);
    }

    /**
     * Returns true if the provided class uses any of the caches provided by Eloquence.
     *
     * @param string $class
     * @return bool
     */
    private function usesCaches(string $class): bool
    {
        return (bool) array_intersect(class_uses_recursive($class), array_keys($this->caches));
    }
}


================================================
FILE: tests/Acceptance/AcceptanceTestCase.php
================================================
<?php
namespace Tests\Acceptance;

use Eloquence\EloquenceServiceProvider;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Support\Facades\Schema;
use Orchestra\Testbench\TestCase;

class AcceptanceTestCase extends TestCase
{
    public function setUp(): void
    {
        parent::setUp();

        $this->migrate();
        $this->init();
    }

    protected function getPackageProviders($app)
    {
        return [
            EloquenceServiceProvider::class,
        ];
    }

    protected function getEnvironmentSetUp($app)
    {
        $app['config']->set('database.default', 'test');
        $app['config']->set('database.connections.test', array(
            'driver'   => 'sqlite',
            'database' => ':memory:'
        ));
    }

    protected function init()
    {
        // Overload
    }

    private function migrate()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('first_name')->nullable();
            $table->string('last_name')->nullable();
            $table->string('slug')->nullable();
            $table->integer('comment_count')->default(0);
            $table->integer('post_count')->default(0);
            $table->timestamps();
        });

        Schema::create('posts', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('category_id')->nullable();
            $table->integer('user_id')->nullable();
            $table->string('slug')->nullable();
            $table->integer('comment_count')->default(0);
            $table->dateTime('publish_at')->nullable();
            $table->timestamps();
        });

        Schema::create('comments', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('user_id');
            $table->integer('post_id');
            $table->timestamps();
            $table->softDeletes();
        });

        Schema::create('orders', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('total_amount')->default(0);
            $table->timestamps();
        });

        Schema::create('items', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('order_id')->nullable();
            $table->integer('amount');
            $table->timestamps();
            $table->softDeletes();
        });

        Schema::create('categories', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('post_count')->default(0);
            $table->integer('total_comments')->default(0);
            $table->dateTime('last_activity_at')->nullable();
            $table->timestamps();
        });
    }
}

================================================
FILE: tests/Acceptance/ChainedAggregatesTest.php
================================================
<?php

namespace Tests\Acceptance;

use Tests\Acceptance\Models\Category;
use Tests\Acceptance\Models\Comment;

class ChainedAggregatesTest extends AcceptanceTestCase
{
    function test_aggregateDependentsAreUpdated()
    {
        // Comment will create a user, and a post - the post created will bubble up to category and update it as well.
        Comment::factory()->create();

        $this->assertSame(1, Category::first()->postCount);
        $this->assertSame(1, Category::first()->totalComments);
    }
}

================================================
FILE: tests/Acceptance/CountCacheTest.php
================================================
<?php
namespace Tests\Acceptance;

use Tests\Acceptance\Models\Comment;
use Tests\Acceptance\Models\Post;
use Tests\Acceptance\Models\User;

class CountCacheTest extends AcceptanceTestCase
{
    function test_userHasASinglePostCount()
    {
        Post::factory()->create();

        $this->assertEquals(1, User::first()->postCount);
    }

    function test_whenRelatedModelsAreSwitchedBothCountCachesAreUpdated()
    {
        $user1 = User::factory()->create();
        $user2 = User::factory()->create();
        $posts = Post::factory()->count(2)->for($user1)->create();
        $comment = Comment::factory()->for($user1)->for($posts->first())->create();

        $this->assertEquals(2, $user1->fresh()->postCount);
        $this->assertEquals(1, $user1->fresh()->commentCount);
        $this->assertEquals(1, $posts->first()->fresh()->commentCount);

        $comment = $comment->fresh();
        $comment->userId = $user2->id;
        $comment->save();

        $this->assertEquals(0, $user1->fresh()->commentCount);
        $this->assertEquals(1, $user2->fresh()->commentCount);
    }

    public function test_itCanHandleModelRestoration()
    {
        $post = Post::factory()->create();

        $comment = Comment::factory()->for($post)->create();
        $comment->delete();
        $comment->restore();

        $this->assertEquals(1, $post->fresh()->commentCount);
    }

    public function test_cacheIsNotUsedWhenRelatedFieldIsNull()
    {
        $user1 = User::factory()->create();
        $posts = Post::factory()->count(2)->for($user1)->create();

        $this->assertEquals(2, $user1->fresh()->postCount);

        $firstPost = $posts->first()->fresh();
        $firstPost->userId = null;
        $firstPost->save();

        $this->assertEquals(1, $user1->fresh()->postCount);
    }

    public function test_canCreateModelWithoutRelatedBehavioursModels()
    {
        $post = new Post();
        $post->save();

        $this->assertModelExists($post);
    }
}

================================================
FILE: tests/Acceptance/GuardedColumnsTest.php
================================================
<?php

namespace Tests\Acceptance;

use Tests\Acceptance\Models\GuardedUser;

class GuardedColumnsTest extends AcceptanceTestCase
{
    public function testGuardedUser()
    {
        $user = GuardedUser::create([
            'firstName' => 'Stuart',
            'last_name' => 'Jones',
        ]);

        $this->assertEquals('Stuart', $user->firstName);
        $this->assertEquals('Jones', $user->lastName);
    }
}


================================================
FILE: tests/Acceptance/HasSlugsTest.php
================================================
<?php
namespace Tests\Acceptance;

use Tests\Acceptance\Models\Post;
use Tests\Acceptance\Models\User;

class HasSlugsTest extends AcceptanceTestCase
{
    function test_slugsCanBeGeneratedWithCustomStrategy()
    {
        $user = User::factory(['firstName' => 'Kirk', 'lastName' => 'Bushell'])->create();

        $this->assertEquals('kirk-bushell', $user->slug);
    }

    function test_slugsCanBeGeneratedUsingRandomValues()
    {
        $post = Post::factory()->create();

        $this->assertMatchesRegularExpression('/^[a-z0-9]{8}$/i', $post->slug);
    }
}


================================================
FILE: tests/Acceptance/Models/Category.php
================================================
<?php

namespace Tests\Acceptance\Models;

use Eloquence\Behaviours\HasCamelCasing;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Category extends Model
{
    use HasCamelCasing;
    use HasFactory;

    protected $fillable = [
        'post_count'
    ];

    protected static function newFactory(): Factory
    {
        return CategoryFactory::new();
    }
}


================================================
FILE: tests/Acceptance/Models/CategoryFactory.php
================================================
<?php

namespace Tests\Acceptance\Models;

use Illuminate\Database\Eloquent\Factories\Factory;

class CategoryFactory extends Factory
{
    protected $model = Category::class;

    public function definition()
    {
        return [];
    }
}

================================================
FILE: tests/Acceptance/Models/Comment.php
================================================
<?php
namespace Tests\Acceptance\Models;

use Eloquence\Behaviours\CountCache\CountedBy;
use Eloquence\Behaviours\CountCache\HasCounts;
use Eloquence\Behaviours\HasCamelCasing;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;

class Comment extends Model
{
    use HasCamelCasing;
    use HasCounts;
    use HasFactory;
    use SoftDeletes;

    protected $fillable = [
        'user_id',
        'post_id',
    ];

    #[CountedBy]
    public function post(): BelongsTo
    {
        return $this->belongsTo(Post::class);
    }

    #[CountedBy]
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    protected static function newFactory(): Factory
    {
        return CommentFactory::new();
    }
}


================================================
FILE: tests/Acceptance/Models/CommentFactory.php
================================================
<?php

namespace Tests\Acceptance\Models;

use Illuminate\Database\Eloquent\Factories\Factory;

class CommentFactory extends Factory
{
    protected $model = Comment::class;

    public function definition()
    {
        return [
            'post_id' => Post::factory(),
            'user_id' => User::factory(),
        ];
    }
}

================================================
FILE: tests/Acceptance/Models/GuardedUser.php
================================================
<?php
namespace Tests\Acceptance\Models;

use Eloquence\Behaviours\HasCamelCasing;
use Illuminate\Database\Eloquent\Model;

class GuardedUser extends Model
{
    use HasCamelCasing;

    protected $table = 'users';

    /**
     * The attributes that are protected from mass assignment.
     *
     * @var array
     */
    protected $guarded = ['id'];
}


================================================
FILE: tests/Acceptance/Models/Item.php
================================================
<?php
namespace Tests\Acceptance\Models;

use Eloquence\Behaviours\SumCache\HasSums;
use Eloquence\Behaviours\HasCamelCasing;
use Eloquence\Behaviours\SumCache\SummedBy;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Item extends Model
{
    use HasCamelCasing;
    use HasSums;
    use HasFactory;
    use SoftDeletes;

    protected $fillable = [
        'amount',
        'order_id',
    ];

    #[SummedBy('amount', 'total_amount')]
    public function order()
    {
        return $this->belongsTo(Order::class);
    }

    protected static function newFactory(): Factory
    {
        return ItemFactory::new();
    }
}


================================================
FILE: tests/Acceptance/Models/ItemFactory.php
================================================
<?php

namespace Tests\Acceptance\Models;

use Illuminate\Database\Eloquent\Factories\Factory;

class ItemFactory extends Factory
{
    protected $model = Item::class;
    
    public function definition(): array
    {
        return [
            'order_id' => Order::factory(),
            'amount' => fake()->numberBetween(0, 100),
        ];
    }
}

================================================
FILE: tests/Acceptance/Models/Order.php
================================================
<?php
namespace Tests\Acceptance\Models;

use Eloquence\Behaviours\HasCamelCasing;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
    use HasCamelCasing;
    use HasFactory;

    public function items()
    {
        return $this->hasMany(Item::class);
    }

    protected static function newFactory(): Factory
    {
        return OrderFactory::new();
    }
}


================================================
FILE: tests/Acceptance/Models/OrderFactory.php
================================================
<?php

namespace Tests\Acceptance\Models;

use Illuminate\Database\Eloquent\Factories\Factory;

class OrderFactory extends Factory
{
    protected $model = Order::class;
    
    public function definition()
    {
        return [];
    }
}

================================================
FILE: tests/Acceptance/Models/Post.php
================================================
<?php
namespace Tests\Acceptance\Models;

use Eloquence\Behaviours\CountCache\CountedBy;
use Eloquence\Behaviours\CountCache\HasCounts;
use Eloquence\Behaviours\HasCamelCasing;
use Eloquence\Behaviours\HasSlugs;
use Eloquence\Behaviours\SumCache\HasSums;
use Eloquence\Behaviours\SumCache\SummedBy;
use Eloquence\Behaviours\ValueCache\HasValues;
use Eloquence\Behaviours\ValueCache\ValuedBy;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Post extends Model
{
    use HasCamelCasing;
    use HasSlugs;
    use HasCounts;
    use HasFactory;
    use HasSums;
    use HasValues;

    protected $fillable = [
        'user_id',
        'category_id',
        'publish_at',
    ];

    #[CountedBy(as: 'post_count')]
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    public function slugStrategy()
    {
        return 'id';
    }

    #[CountedBy]
    #[SummedBy(from: 'comment_count', as: 'total_comments')]
    #[ValuedBy(from: 'publish_at', as: 'last_activity_at')]
    public function category(): BelongsTo
    {
        return $this->belongsTo(Category::class);
    }

    protected static function newFactory(): Factory
    {
        return PostFactory::new();
    }
}


================================================
FILE: tests/Acceptance/Models/PostFactory.php
================================================
<?php

namespace Tests\Acceptance\Models;

use Illuminate\Database\Eloquent\Factories\Factory;

class PostFactory extends Factory
{
    protected $model = Post::class;
    
    public function definition(): array
    {
        return [
            'category_id' => Category::factory(),
            'user_id' => User::factory(),
        ];
    }
}

================================================
FILE: tests/Acceptance/Models/Role.php
================================================
<?php

namespace Tests\Acceptance\Models;

use Eloquence\Behaviours\HasCamelCasing;
use Illuminate\Database\Eloquent\Model;

class Role extends Model
{
    use HasCamelCasing;

    
}

================================================
FILE: tests/Acceptance/Models/User.php
================================================
<?php
namespace Tests\Acceptance\Models;

use Eloquence\Behaviours\HasCamelCasing;
use Eloquence\Behaviours\HasSlugs;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

class User extends Model
{
    use HasCamelCasing;
    use HasFactory;
    use HasSlugs;

    protected $fillable = [
        'post_count'
    ];

    public function posts(): HasMany
    {
        return $this->hasMany(Post::class);
    }

    public function slugStrategy()
    {
        return ['firstName', 'lastName'];
    }

    protected static function newFactory(): Factory
    {
        return UserFactory::new();
    }
}


================================================
FILE: tests/Acceptance/Models/UserFactory.php
================================================
<?php

namespace Tests\Acceptance\Models;

use Illuminate\Database\Eloquent\Factories\Factory;

class UserFactory extends Factory
{
    protected $model = User::class;
    
    public function definition()
    {
        return [
            'firstName' => fake()->firstName(),
            'lastName' => fake()->lastName(),
        ];
    }
}

================================================
FILE: tests/Acceptance/RebuildCacheTest.php
================================================
<?php

namespace Tests\Acceptance;

use Tests\Acceptance\Models\Item;
use Tests\Acceptance\Models\Order;
use Tests\Acceptance\Models\Post;
use Tests\Acceptance\Models\User;

class RebuildCacheTest extends AcceptanceTestCase
{
    function test_countCachesCanBeRebuilt()
    {
        $user1 = User::factory()->create();
        $user2 = User::factory()->create();

        Post::factory()->count(5)->for($user1)->create();
        Post::factory()->count(2)->for($user2)->create();

        $user1->postCount = 0;
        $user1->save();
        $user2->postCount = 3;
        $user2->save();

        Post::rebuildCountCache();

        $this->assertEquals(5, $user1->fresh()->postCount);
        $this->assertEquals(2, $user2->fresh()->postCount);
    }

    function test_sumCachesCanBeRebuilt()
    {
        $order = Order::factory()->create();
        Item::factory()->count(3)->for($order)->create(['amount' => 10]);

        $order->totalAmount = 50;
        $order->save();

        $this->assertEquals(50, $order->fresh()->totalAmount);

        Item::rebuildSumCache();

        $this->assertEquals(30, $order->fresh()->totalAmount);
    }
}

================================================
FILE: tests/Acceptance/RebuildCachesCommandTest.php
================================================
<?php

namespace Tests\Acceptance;

use Tests\Acceptance\Models\Item;
use Tests\Acceptance\Models\Order;
use Tests\Acceptance\Models\Post;
use Tests\Acceptance\Models\User;

class RebuildCachesCommandTest extends AcceptanceTestCase
{
    function test_itCanRebuildCachesOfAllAffectedModels()
    {
        $order1 = Order::factory()->create(['total_amount' => 0]);
        $order2 = Order::factory()->create(['total_amount' => 0]);

        Item::factory()->for($order1)->count(10)->create(['amount' => 10]);
        Item::factory()->for($order2)->count(5)->create(['amount' => 5]);

        $user1 = User::factory()->create();
        $user2 = User::factory()->create();

        Post::factory()->for($user1)->count(10)->create();
        Post::factory()->for($user2)->count(5)->create();

        $order1->totalAmount = 0;
        $order1->save();

        $order2->totalAmount = 0;
        $order2->save();

        $result = $this->artisan('eloquence:rebuild-caches '.__DIR__.'/../../tests/Acceptance/Models');

        $result->assertExitCode(0);

        $this->assertDatabaseHas('users', ['post_count' => 10]);
        $this->assertDatabaseHas('users', ['post_count' => 5]);
        $this->assertDatabaseHas('orders', ['total_amount' => 100]);
        $this->assertDatabaseHas('orders', ['total_amount' => 25]);
    }
}

================================================
FILE: tests/Acceptance/SumCacheTest.php
================================================
<?php
namespace Tests\Acceptance;

use Tests\Acceptance\Models\Item;
use Tests\Acceptance\Models\Order;

class SumCacheTest extends AcceptanceTestCase
{
    function test_relatedModelSumCacheIsIncreasedWhenModelIsCreated()
    {
        Item::factory()->create(['amount' => 34]);

        $this->assertEquals(34, Order::first()->totalAmount);
    }

    function test_relatedModelSumCacheIsDecreasedWhenModelIsDeleted()
    {
        $item = Item::factory()->create(['amount' => 19]);
        $item->delete();

        $this->assertEquals(0, Order::first()->totalAmount);
    }

    function test_whenAnAggregatedModelValueSwitchesContext()
    {
        $item = Item::factory()->create();
        $newOrder = Order::factory()->create();

        $item = $item->fresh();
        $item->orderId = $newOrder->id;
        $item->save();

        $this->assertEquals(0, Order::first()->totalAmount);
        $this->assertEquals($item->amount, $newOrder->fresh()->totalAmount);
    }

    function test_aggregateValuesAreUpdatedWhenModelsAreRestored()
    {
        $item = Item::factory()->create();
        $item->delete(); // Triggers decrease in order total
        $item->restore(); // Restores order total

        $this->assertEquals($item->amount, Order::first()->totalAmount);
    }

    function test_aggregateValueIsSetToCorrectAmountWhenSourceFieldChanges()
    {
        $item = Item::factory()->create();
        $item->amount = 20;
        $item->save();

        $this->assertEquals(20, Order::first()->totalAmount);
    }

    function test_aggregateValueOnOriginalRelatedModelIsUpdatedCorrectlyWhenTheForeignKeyAndAmountIsChanged()
    {
        $item = Item::factory()->create();

        $newOrder = Order::factory()->create();

        $item = $item->fresh();
        $item->amount = 20;
        $item->orderId = $newOrder->id;
        $item->save();

        $this->assertEquals(0, Order::first()->totalAmount);
        $this->assertEquals(20, $newOrder->fresh()->totalAmount);
    }

    public function test_cacheIsNotUsedWhenRelatedFieldIsNull()
    {
        $order = Order::factory()->create();
        $items = Item::factory()->count(5)->for($order)->create(['amount' => 1]);

        $this->assertEquals(5, Order::first()->totalAmount);

        $items->first()->order_id = null;
        $items->first()->save();

        $this->assertEquals(4, $order->fresh()->totalAmount);
    }
}

================================================
FILE: tests/Acceptance/ValueCacheTest.php
================================================
<?php

namespace Tests\Acceptance;

use Tests\Acceptance\Models\Category;
use Tests\Acceptance\Models\Post;

class ValueCacheTest extends AcceptanceTestCase
{
    function test_values_from_related_models_are_cached()
    {
        $category = Category::factory()->create();

        $this->assertNull($category->last_activity_at);

        $post = Post::factory()->create(['category_id' => $category->id, 'publish_at' => now()->subDays(mt_rand(1, 10))]);

        $this->assertEquals($category->fresh()->last_activity_at, $post->publish_at);
    }
}


================================================
FILE: tests/Unit/Behaviours/ReadOnly/HasReadOnlyTest.php
================================================
<?php

namespace Tests\Unit\Behaviours\ReadOnly;

use Eloquence\Behaviours\ReadOnly\WriteAccessDenied;
use Tests\Unit\Stubs\ReadOnlyModelStub;
use Tests\Unit\TestCase;

final class HasReadOnlyTest extends TestCase
{
    function test_attributes_cannot_be_set()
    {
        $this->expectException(WriteAccessDenied::class);

        new ReadOnlyModelStub(['value' => 1]);
    }

    function test_model_cannot_be_saved()
    {
        $this->expectException(WriteAccessDenied::class);

        $model = new ReadOnlyModelStub;
        $model->save();
    }
}

================================================
FILE: tests/Unit/Behaviours/SlugTest.php
================================================
<?php
namespace tests\Unit\Database\Traits;

use Eloquence\Behaviours\Slug;
use Tests\Unit\TestCase;

class SlugTest extends TestCase
{
    public function test_random_slug_is_random()
    {
        $this->assertNotEquals(Slug::random(), Slug::random());
    }

    public function test_slugs_are_8_characters_long()
    {
        $this->assertEquals(8, strlen((string) Slug::random()));
    }
}


================================================
FILE: tests/Unit/Database/Traits/HasCamelCasingTest.php
================================================
<?php
namespace tests\Unit\Database\Traits;

use Carbon\Carbon;
use Tests\Unit\Stubs\ModelStub;
use Tests\Unit\Stubs\PivotModelStub;
use Tests\Unit\Stubs\RealModelStub;
use Tests\Unit\TestCase;

class HasCamelCasingTest extends TestCase
{
    private $model;

    public function init()
    {
        date_default_timezone_set('Australia/Sydney');

        $this->model = new ModelStub;
    }

    public function test_attributes_as_array()
    {
        $attributes = $this->model->attributesToArray();

        $this->assertArrayHasKey('firstName', $attributes);
        $this->assertArrayHasKey('lastName', $attributes);
        $this->assertArrayHasKey('address', $attributes);
        $this->assertArrayHasKey('firstName', $attributes);
    }

    public function test_attribute_declaration()
    {
        $this->model->setAttribute('firstName', 'Andrew');

        $this->assertEquals('Andrew', $this->model->getAttribute('firstName'));
    }

    public function test_attribute_retrieval()
    {
        $this->assertEquals('Kirk', $this->model->getAttribute('firstName'));
    }

    public function test_attribute_conversion()
    {
        $expectedAttributes = [
            'address' => 'Home',
            'countryOfOrigin' => 'Australia',
            'firstName' => 'Kirk',
            'lastName' => 'Bushell'
        ];

        $this->assertEquals($expectedAttributes, $this->model->attributesToArray());
    }

    public function test_attribute_conversion_leaves_pivots()
    {
        $model = new PivotModelStub;

        $expectedAttributes = [
            'firstName' => 'Kirk',
            'pivot_field' => 'whatever'
        ];

        $this->assertEquals($expectedAttributes, $model->attributesToArray());
    }

    public function test_model_filling()
    {
        $model = new RealModelStub([
            'myField' => 'value',
            'anotherField' => 'yeah',
            'someField' => 'whatever'
        ]);

        $this->assertEquals($model->myField, 'value');
        $this->assertEquals($model->anotherField, 'yeah');
        $this->assertNull($model->someField);
    }

    public function test_isset_unset()
    {
        $model = new RealModelStub;

        // initial check
        $this->assertFalse(isset($model->my_field) || isset($model->myField));

        // snake_case set
        $model->my_field = 'value';
        $this->assertTrue(isset($model->my_field) && isset($model->myField));

        // snake_case unset
        unset($model->my_field);
        $this->assertFalse(isset($model->my_field) || isset($model->myField));

        // camelCase set
        $model->myField = 'value';
        $this->assertTrue(isset($model->my_field) && isset($model->myField));

        // camelCase unset
        unset($model->myField);
        $this->assertFalse(isset($model->my_field) || isset($model->myField));
    }

    public function test_model_hidden_fields()
    {
        $model = new RealModelStub([
            'myField' => 'value',
            'anotherField' => 'yeah',
            'someField' => 'whatever',
            'hiddenField' => 'secrets!',
            'passwordHash' => '1234',
        ]);

        $modelArray = $model->toArray();

        $this->assertFalse(isset($modelArray['hiddenField']));
        $this->assertFalse(isset($modelArray['passwordHash']));

        $this->assertEquals('secrets!', $model->getAttribute('hiddenField'));
        $this->assertEquals('1234', $model->getAttribute('passwordHash'));
    }

    public function test_model_date_handling()
    {
        $model = new RealModelStub([
            'myField' => '2011-11-11T11:11:11Z',
            'dateField' => '2011-11-11T11:11:11Z',
        ]);

        $this->assertFalse($model->myField instanceof Carbon);
        $this->assertTrue($model->dateField instanceof Carbon);
    }
}


================================================
FILE: tests/Unit/Stubs/CountCache/Comment.php
================================================
<?php
namespace Tests\Unit\Stubs\CountCache;

use Eloquence\Behaviours\CountCache\HasCounts;
use Eloquence\Database\Model;

class Comment extends Model
{
    use HasCounts;

    public function countCaches()
    {
        return [
            'num_comments' => 'Tests\Unit\Stubs\CountCache\Post',
            'Tests\Unit\Stubs\CountCache\User'
        ];
    }
}


================================================
FILE: tests/Unit/Stubs/CountCache/Post.php
================================================
<?php
namespace Tests\Unit\Stubs\CountCache;

use Eloquence\Behaviours\CountCache\HasCounts;
use Eloquence\Database\Model;

class Post extends Model
{
    use HasCounts;

    public function countCaches()
    {
        return [
            'posts_count' => ['Tests\Unit\Stubs\CountCache\User', 'user_id', 'id'],
            [
                'model' => 'Tests\Unit\Stubs\CountCache\User',
                'countField' => 'posts_count_explicit',
                'foreignKey' => 'user_id',
                'key' => 'id'
            ]
        ];
    }
}


================================================
FILE: tests/Unit/Stubs/CountCache/User.php
================================================
<?php
namespace Tests\Unit\Stubs\CountCache;

use Eloquence\Behaviours\HasSlugs;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    use HasSlugs;

    public function slugStrategy()
    {
        return ['id'];
    }
}


================================================
FILE: tests/Unit/Stubs/ModelStub.php
================================================
<?php
namespace Tests\Unit\Stubs;

use Eloquence\Behaviours\HasCamelCasing;

class ModelStub extends ParentModelStub
{
    use \Eloquence\Behaviours\HasCamelCasing;

    protected $attributes = [
        'first_name' => 'Kirk',
        'last_name' => 'Bushell',
        'address' => 'Home',
        'country_of_origin' => 'Australia'
    ];
}


================================================
FILE: tests/Unit/Stubs/ParentModelStub.php
================================================
<?php
namespace Tests\Unit\Stubs;

class ParentModelStub
{
    public function attributesToArray()
    {
        return $this->attributes;
    }

    public function getAttribute($key)
    {
        return $this->attributes[$key];
    }

    public function setAttribute($key, $value)
    {
        $this->attributes[$key] = $value;
    }

    public function isRelation($key)
    {
        return false;
    }
}

================================================
FILE: tests/Unit/Stubs/PivotModelStub.php
================================================
<?php
namespace Tests\Unit\Stubs;

use Eloquence\Behaviours\HasCamelCasing;

class PivotModelStub extends ParentModelStub
{
    use \Eloquence\Behaviours\HasCamelCasing;

    protected $attributes = [
        'first_name' => 'Kirk',
        'pivot_field' => 'whatever'
    ];
}


================================================
FILE: tests/Unit/Stubs/ReadOnlyModelStub.php
================================================
<?php

namespace Tests\Unit\Stubs;

use Eloquence\Behaviours\ReadOnly\HasReadOnly;
use Illuminate\Database\Eloquent\Model;

final class ReadOnlyModelStub extends Model
{
    use HasReadOnly;

    protected $fillable = [
        'value'
    ];
}

================================================
FILE: tests/Unit/Stubs/RealModelStub.php
================================================
<?php
namespace Tests\Unit\Stubs;

use Eloquence\Behaviours\CountCache\CacheConfig;
use Eloquence\Behaviours\CountCache\HasCounts;
use Eloquence\Behaviours\CountCache\CountCache;
use Eloquence\Database\Model;

class RealModelStub extends Model
{
    use \Eloquence\Behaviours\CountCache\HasCounts;

    protected $dateFormat = \DateTime::ISO8601;

    protected $casts = [
        'dateField' => 'datetime',
    ];

    public $hidden = ['hiddenField', 'passwordHash'];

    public $fillable = ['myField', 'anotherField', 'some_field', 'hiddenField', 'passwordHash', 'dateField'];

    public function fakeRelationship()
    {
        return 'nothing';
    }

    /**
     * Should return an array of the count caches that need to be updated when this
     * model's state changes. Use the following array below as an example when a User
     * needs to update a Role's user count cache. These represent the default values used
     * by the behaviour.
     *
     *   return [ 'user_count' => [ 'Role', 'role_id', 'id' ] ];
     *
     * So, to extend, the first argument should be an index representing the counter cache
     * field on the associated model. Next is a numerical array:
     *
     * 0 = The model to be used for the update
     * 1 = The foreign_key for the relationship that RelatedCount will watch *optional
     * 2 = The remote field that represents the key *optional
     *
     * If the latter 2 options are not provided, or if the counter cache option is a string representing
     * the model, then RelatedCount will assume the ID fields based on conventional standards.
     *
     * Ie. another way to setup a counter cache is like below. This is an identical configuration to above.
     *
     *   return [ 'user_count' => 'Role' ];
     *
     * This can be simplified even further, like this:
     *
     *   return [ 'Role' ];
     *
     * @return array
     */
    public function countCaches()
    {
        return [
            'users_count' => ['Role', 'role_id', 'id'],
            'comments_count' => 'Post',
            'User'
        ];
    }
}


================================================
FILE: tests/Unit/Stubs/SumCache/Item.php
================================================
<?php
namespace Tests\Unit\Stubs\SumCache;

use Eloquence\Behaviours\SumCache\HasSums;
use Eloquence\Database\Model;

class Item extends Model
{
    use HasSums;

    public function sumCaches()
    {
        return [
            'Tests\Unit\Stubs\SumCache\Order',
            [
                'model' => 'Tests\Unit\Stubs\SumCache\Order',
                'sumField' => 'itemTotalExplicit',
                'columnToSum' => 'total',
                'foreignKey' => 'itemId',
                'key' => 'id',
            ]
        ];
    }
}


================================================
FILE: tests/Unit/Stubs/SumCache/Order.php
================================================
<?php
namespace Tests\Unit\Stubs\SumCache;

use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
}


================================================
FILE: tests/Unit/TestCase.php
================================================
<?php
namespace Tests\Unit;

use PHPUnit\Framework\TestCase as PHPUnit_Framework_TestCase;
use Mockery as m;

class TestCase extends PHPUnit_Framework_TestCase
{
    public function setUp(): void
    {
        parent::setUp();

        $this->init();
    }

    public function tearDown(): void
    {
        m::close();
    }

    public function init()
    {
        // Nothing to do - for children to implement.
    }
}
Download .txt
gitextract_qf5a7nt9/

├── .github/
│   └── workflows/
│       └── test.yml
├── .gitignore
├── LICENSE
├── README.md
├── composer.json
├── config/
│   └── eloquence.php
├── phpunit.xml
├── src/
│   ├── Behaviours/
│   │   ├── CacheConfig.php
│   │   ├── Cacheable.php
│   │   ├── CountCache/
│   │   │   ├── CountCache.php
│   │   │   ├── CountedBy.php
│   │   │   ├── HasCounts.php
│   │   │   └── Observer.php
│   │   ├── HasCamelCasing.php
│   │   ├── HasSlugs.php
│   │   ├── ReadOnly/
│   │   │   ├── HasReadOnly.php
│   │   │   └── WriteAccessDenied.php
│   │   ├── Slug.php
│   │   ├── SumCache/
│   │   │   ├── HasSums.php
│   │   │   ├── Observer.php
│   │   │   ├── SumCache.php
│   │   │   ├── Summable.php
│   │   │   └── SummedBy.php
│   │   └── ValueCache/
│   │       ├── HasValues.php
│   │       ├── Observer.php
│   │       ├── ValueCache.php
│   │       └── ValuedBy.php
│   ├── Database/
│   │   └── Model.php
│   ├── EloquenceServiceProvider.php
│   ├── Exceptions/
│   │   └── UnableToCreateSlugException.php
│   └── Utilities/
│       ├── DBQueryLog.php
│       └── RebuildCaches.php
└── tests/
    ├── Acceptance/
    │   ├── AcceptanceTestCase.php
    │   ├── ChainedAggregatesTest.php
    │   ├── CountCacheTest.php
    │   ├── GuardedColumnsTest.php
    │   ├── HasSlugsTest.php
    │   ├── Models/
    │   │   ├── Category.php
    │   │   ├── CategoryFactory.php
    │   │   ├── Comment.php
    │   │   ├── CommentFactory.php
    │   │   ├── GuardedUser.php
    │   │   ├── Item.php
    │   │   ├── ItemFactory.php
    │   │   ├── Order.php
    │   │   ├── OrderFactory.php
    │   │   ├── Post.php
    │   │   ├── PostFactory.php
    │   │   ├── Role.php
    │   │   ├── User.php
    │   │   └── UserFactory.php
    │   ├── RebuildCacheTest.php
    │   ├── RebuildCachesCommandTest.php
    │   ├── SumCacheTest.php
    │   └── ValueCacheTest.php
    └── Unit/
        ├── Behaviours/
        │   ├── ReadOnly/
        │   │   └── HasReadOnlyTest.php
        │   └── SlugTest.php
        ├── Database/
        │   └── Traits/
        │       └── HasCamelCasingTest.php
        ├── Stubs/
        │   ├── CountCache/
        │   │   ├── Comment.php
        │   │   ├── Post.php
        │   │   └── User.php
        │   ├── ModelStub.php
        │   ├── ParentModelStub.php
        │   ├── PivotModelStub.php
        │   ├── ReadOnlyModelStub.php
        │   ├── RealModelStub.php
        │   └── SumCache/
        │       ├── Item.php
        │       └── Order.php
        └── TestCase.php
Download .txt
SYMBOL INDEX (229 symbols across 62 files)

FILE: src/Behaviours/CacheConfig.php
  class CacheConfig (line 8) | class CacheConfig
    method __construct (line 10) | public function __construct(readonly string $relationName, readonly st...
    method relation (line 18) | public function relation(Model $model): Relation
    method relatedModel (line 26) | public function relatedModel(Model $model): ?Model
    method emptyRelatedModel (line 35) | public function emptyRelatedModel(Model $model): Model
    method relatedModelClass (line 43) | public function relatedModelClass($model): string
    method foreignKeyName (line 48) | public function foreignKeyName(Model $model): string

FILE: src/Behaviours/Cacheable.php
  type Cacheable (line 16) | trait Cacheable
    method configuration (line 23) | abstract private function configuration(): array;
    method config (line 28) | protected function config($relationName, $aggregateField): CacheConfig
    method for (line 36) | public static function for(Model $model): self
    method reflect (line 41) | public function reflect(string $attributeClass, \Closure $fn)
    method apply (line 65) | protected function apply(Closure $function): void
    method updateCacheRecord (line 77) | protected function updateCacheRecord(Model $model, CacheConfig $config...
    method rebuildCacheRecord (line 89) | public function rebuildCacheRecord(CacheConfig $config, Model $model, ...
    method updateCacheValue (line 112) | protected function updateCacheValue(?Model $model, CacheConfig $config...

FILE: src/Behaviours/CountCache/CountCache.php
  class CountCache (line 13) | class CountCache
    method __construct (line 17) | private function __construct(private Model $model)
    method configuration (line 21) | private function configuration(): array
    method update (line 33) | public function update(): void
    method rebuild (line 58) | public function rebuild(): void
    method increment (line 65) | public function increment(): void
    method decrement (line 72) | public function decrement(): void

FILE: src/Behaviours/CountCache/CountedBy.php
  class CountedBy (line 7) | #[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
    method __construct (line 10) | public function __construct(readonly ?string $as = null)

FILE: src/Behaviours/CountCache/HasCounts.php
  type HasCounts (line 5) | trait HasCounts
    method bootHasCounts (line 7) | public static function bootHasCounts(): void
    method rebuildCountCache (line 12) | public static function rebuildCountCache(): void

FILE: src/Behaviours/CountCache/Observer.php
  class Observer (line 9) | class Observer
    method created (line 16) | public function created($model): void
    method deleted (line 26) | public function deleted($model): void
    method updated (line 36) | public function updated($model): void
    method restored (line 46) | public function restored($model): void

FILE: src/Behaviours/HasCamelCasing.php
  type HasCamelCasing (line 7) | trait HasCamelCasing
    method isGuardableColumn (line 24) | protected function isGuardableColumn($key)
    method setAttribute (line 38) | public function setAttribute($key, $value)
    method getAttribute (line 49) | public function getAttribute($key): mixed
    method attributesToArray (line 59) | public function attributesToArray()
    method relationsToArray (line 69) | public function relationsToArray()
    method getHidden (line 81) | public function getHidden()
    method getCasts (line 92) | public function getCasts()
    method toCamelCase (line 107) | public function toCamelCase($attributes)
    method toSnakeCase (line 125) | public function toSnakeCase($attributes)
    method getTrueKey (line 142) | public function getTrueKey($key)
    method isCamelCase (line 159) | public function isCamelCase()
    method getSnakeKey (line 170) | protected function getSnakeKey($key)
    method __isset (line 182) | public function __isset($key)
    method __unset (line 194) | public function __unset($key)

FILE: src/Behaviours/HasSlugs.php
  type HasSlugs (line 8) | trait HasSlugs
    method bootHasSlugs (line 14) | public static function bootHasSlugs(): void
    method generateIdSlug (line 24) | public function generateIdSlug(): void
    method generateTitleSlug (line 47) | public function generateTitleSlug(array $fields): void
    method getTitleFields (line 67) | public function getTitleFields(array $fields): array
    method generateSlug (line 81) | public function generateSlug(): void
    method setSlugValue (line 92) | public function setSlugValue(Slug $value): void
    method getRouteKey (line 100) | public function getRouteKey(): mixed
    method slugField (line 110) | protected function slugField(): string
    method slugStrategy (line 137) | public function slugStrategy(): string
    method slugExists (line 142) | private function slugExists(Slug $slug): bool

FILE: src/Behaviours/ReadOnly/HasReadOnly.php
  type HasReadOnly (line 5) | trait HasReadOnly
    method setAttribute (line 7) | public function setAttribute($key, $value)
    method save (line 12) | public function save(array $options = [])

FILE: src/Behaviours/ReadOnly/WriteAccessDenied.php
  class WriteAccessDenied (line 5) | final class WriteAccessDenied extends \RuntimeException
    method __construct (line 7) | public function __construct(string $model)

FILE: src/Behaviours/Slug.php
  class Slug (line 11) | class Slug implements Castable, Jsonable
    method __construct (line 24) | public function __construct($slug)
    method fromId (line 35) | public static function fromId($id)
    method random (line 47) | public static function random(): Slug
    method fromTitle (line 62) | public static function fromTitle($title): Slug
    method __toString (line 67) | public function __toString(): string
    method toJson (line 72) | public function toJson($options = 0): string
    method castUsing (line 77) | public static function castUsing(array $arguments): CastsAttributes

FILE: src/Behaviours/SumCache/HasSums.php
  type HasSums (line 5) | trait HasSums
    method bootHasSums (line 7) | public static function bootHasSums(): void
    method rebuildSumCache (line 12) | public static function rebuildSumCache(): void

FILE: src/Behaviours/SumCache/Observer.php
  class Observer (line 5) | class Observer
    method created (line 7) | public function created($model)
    method updated (line 12) | public function updated($model)
    method deleted (line 17) | public function deleted($model)
    method restored (line 22) | public function restored($model)

FILE: src/Behaviours/SumCache/SumCache.php
  class SumCache (line 9) | class SumCache
    method __construct (line 13) | private function __construct(private Model $model)
    method configuration (line 17) | private function configuration(): array
    method rebuild (line 27) | public function rebuild(): void
    method increase (line 34) | public function increase(): void
    method decrease (line 41) | public function decrease(): void
    method update (line 51) | public function update(): void
    method config (line 74) | protected function config($relationName, $sourceField): CacheConfig

FILE: src/Behaviours/SumCache/Summable.php
  type Summable (line 5) | interface Summable
    method summedBy (line 26) | public function summedBy(): array;

FILE: src/Behaviours/SumCache/SummedBy.php
  class SummedBy (line 7) | #[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
    method __construct (line 10) | public function __construct(readonly string $from, readonly string $as)

FILE: src/Behaviours/ValueCache/HasValues.php
  type HasValues (line 5) | trait HasValues
    method bootHasValues (line 7) | public static function bootHasValues(): void
    method rebuildValueCache (line 12) | public static function rebuildValueCache(): void

FILE: src/Behaviours/ValueCache/Observer.php
  class Observer (line 5) | class Observer
    method created (line 7) | public function created($model): void
    method updated (line 12) | public function updated($model): void

FILE: src/Behaviours/ValueCache/ValueCache.php
  class ValueCache (line 9) | class ValueCache
    method __construct (line 13) | private function __construct(private Model $model)
    method config (line 17) | protected function config($relationName, $sourceField): CacheConfig
    method rebuild (line 27) | public function rebuild()
    method updateRelated (line 32) | public function updateRelated(bool $new): void
    method configuration (line 51) | private function configuration(): array

FILE: src/Behaviours/ValueCache/ValuedBy.php
  class ValuedBy (line 7) | #[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
    method __construct (line 10) | public function __construct(readonly string $from, readonly ?string $as)

FILE: src/Database/Model.php
  class Model (line 14) | abstract class Model extends \Illuminate\Database\Eloquent\Model

FILE: src/EloquenceServiceProvider.php
  class EloquenceServiceProvider (line 8) | class EloquenceServiceProvider extends ServiceProvider
    method boot (line 10) | public function boot(): void
    method initialiseDbQueryLog (line 20) | protected function initialiseDbQueryLog(): void
    method initialiseCommands (line 25) | private function initialiseCommands(): void

FILE: src/Exceptions/UnableToCreateSlugException.php
  class UnableToCreateSlugException (line 5) | class UnableToCreateSlugException extends \Exception

FILE: src/Utilities/DBQueryLog.php
  class DBQueryLog (line 9) | class DBQueryLog
    method initialise (line 18) | public static function initialise(): void

FILE: src/Utilities/RebuildCaches.php
  class RebuildCaches (line 14) | class RebuildCaches extends Command
    method handle (line 24) | public function handle(): void
    method allModelsUsingCaches (line 47) | private function allModelsUsingCaches(string $path): Collection
    method fullyQualifiedClassName (line 62) | private function fullyQualifiedClassName(SplFileInfo $file)
    method usesCaches (line 95) | private function usesCaches(string $class): bool

FILE: tests/Acceptance/AcceptanceTestCase.php
  class AcceptanceTestCase (line 10) | class AcceptanceTestCase extends TestCase
    method setUp (line 12) | public function setUp(): void
    method getPackageProviders (line 20) | protected function getPackageProviders($app)
    method getEnvironmentSetUp (line 27) | protected function getEnvironmentSetUp($app)
    method init (line 36) | protected function init()
    method migrate (line 41) | private function migrate()

FILE: tests/Acceptance/ChainedAggregatesTest.php
  class ChainedAggregatesTest (line 8) | class ChainedAggregatesTest extends AcceptanceTestCase
    method test_aggregateDependentsAreUpdated (line 10) | function test_aggregateDependentsAreUpdated()

FILE: tests/Acceptance/CountCacheTest.php
  class CountCacheTest (line 8) | class CountCacheTest extends AcceptanceTestCase
    method test_userHasASinglePostCount (line 10) | function test_userHasASinglePostCount()
    method test_whenRelatedModelsAreSwitchedBothCountCachesAreUpdated (line 17) | function test_whenRelatedModelsAreSwitchedBothCountCachesAreUpdated()
    method test_itCanHandleModelRestoration (line 36) | public function test_itCanHandleModelRestoration()
    method test_cacheIsNotUsedWhenRelatedFieldIsNull (line 47) | public function test_cacheIsNotUsedWhenRelatedFieldIsNull()
    method test_canCreateModelWithoutRelatedBehavioursModels (line 61) | public function test_canCreateModelWithoutRelatedBehavioursModels()

FILE: tests/Acceptance/GuardedColumnsTest.php
  class GuardedColumnsTest (line 7) | class GuardedColumnsTest extends AcceptanceTestCase
    method testGuardedUser (line 9) | public function testGuardedUser()

FILE: tests/Acceptance/HasSlugsTest.php
  class HasSlugsTest (line 7) | class HasSlugsTest extends AcceptanceTestCase
    method test_slugsCanBeGeneratedWithCustomStrategy (line 9) | function test_slugsCanBeGeneratedWithCustomStrategy()
    method test_slugsCanBeGeneratedUsingRandomValues (line 16) | function test_slugsCanBeGeneratedUsingRandomValues()

FILE: tests/Acceptance/Models/Category.php
  class Category (line 10) | class Category extends Model
    method newFactory (line 19) | protected static function newFactory(): Factory

FILE: tests/Acceptance/Models/CategoryFactory.php
  class CategoryFactory (line 7) | class CategoryFactory extends Factory
    method definition (line 11) | public function definition()

FILE: tests/Acceptance/Models/Comment.php
  class Comment (line 13) | class Comment extends Model
    method post (line 25) | #[CountedBy]
    method user (line 31) | #[CountedBy]
    method newFactory (line 37) | protected static function newFactory(): Factory

FILE: tests/Acceptance/Models/CommentFactory.php
  class CommentFactory (line 7) | class CommentFactory extends Factory
    method definition (line 11) | public function definition()

FILE: tests/Acceptance/Models/GuardedUser.php
  class GuardedUser (line 7) | class GuardedUser extends Model

FILE: tests/Acceptance/Models/Item.php
  class Item (line 12) | class Item extends Model
    method order (line 24) | #[SummedBy('amount', 'total_amount')]
    method newFactory (line 30) | protected static function newFactory(): Factory

FILE: tests/Acceptance/Models/ItemFactory.php
  class ItemFactory (line 7) | class ItemFactory extends Factory
    method definition (line 11) | public function definition(): array

FILE: tests/Acceptance/Models/Order.php
  class Order (line 9) | class Order extends Model
    method items (line 14) | public function items()
    method newFactory (line 19) | protected static function newFactory(): Factory

FILE: tests/Acceptance/Models/OrderFactory.php
  class OrderFactory (line 7) | class OrderFactory extends Factory
    method definition (line 11) | public function definition()

FILE: tests/Acceptance/Models/Post.php
  class Post (line 17) | class Post extends Model
    method user (line 32) | #[CountedBy(as: 'post_count')]
    method slugStrategy (line 38) | public function slugStrategy()
    method category (line 43) | #[CountedBy]
    method newFactory (line 51) | protected static function newFactory(): Factory

FILE: tests/Acceptance/Models/PostFactory.php
  class PostFactory (line 7) | class PostFactory extends Factory
    method definition (line 11) | public function definition(): array

FILE: tests/Acceptance/Models/Role.php
  class Role (line 8) | class Role extends Model

FILE: tests/Acceptance/Models/User.php
  class User (line 11) | class User extends Model
    method posts (line 21) | public function posts(): HasMany
    method slugStrategy (line 26) | public function slugStrategy()
    method newFactory (line 31) | protected static function newFactory(): Factory

FILE: tests/Acceptance/Models/UserFactory.php
  class UserFactory (line 7) | class UserFactory extends Factory
    method definition (line 11) | public function definition()

FILE: tests/Acceptance/RebuildCacheTest.php
  class RebuildCacheTest (line 10) | class RebuildCacheTest extends AcceptanceTestCase
    method test_countCachesCanBeRebuilt (line 12) | function test_countCachesCanBeRebuilt()
    method test_sumCachesCanBeRebuilt (line 31) | function test_sumCachesCanBeRebuilt()

FILE: tests/Acceptance/RebuildCachesCommandTest.php
  class RebuildCachesCommandTest (line 10) | class RebuildCachesCommandTest extends AcceptanceTestCase
    method test_itCanRebuildCachesOfAllAffectedModels (line 12) | function test_itCanRebuildCachesOfAllAffectedModels()

FILE: tests/Acceptance/SumCacheTest.php
  class SumCacheTest (line 7) | class SumCacheTest extends AcceptanceTestCase
    method test_relatedModelSumCacheIsIncreasedWhenModelIsCreated (line 9) | function test_relatedModelSumCacheIsIncreasedWhenModelIsCreated()
    method test_relatedModelSumCacheIsDecreasedWhenModelIsDeleted (line 16) | function test_relatedModelSumCacheIsDecreasedWhenModelIsDeleted()
    method test_whenAnAggregatedModelValueSwitchesContext (line 24) | function test_whenAnAggregatedModelValueSwitchesContext()
    method test_aggregateValuesAreUpdatedWhenModelsAreRestored (line 37) | function test_aggregateValuesAreUpdatedWhenModelsAreRestored()
    method test_aggregateValueIsSetToCorrectAmountWhenSourceFieldChanges (line 46) | function test_aggregateValueIsSetToCorrectAmountWhenSourceFieldChanges()
    method test_aggregateValueOnOriginalRelatedModelIsUpdatedCorrectlyWhenTheForeignKeyAndAmountIsChanged (line 55) | function test_aggregateValueOnOriginalRelatedModelIsUpdatedCorrectlyWh...
    method test_cacheIsNotUsedWhenRelatedFieldIsNull (line 70) | public function test_cacheIsNotUsedWhenRelatedFieldIsNull()

FILE: tests/Acceptance/ValueCacheTest.php
  class ValueCacheTest (line 8) | class ValueCacheTest extends AcceptanceTestCase
    method test_values_from_related_models_are_cached (line 10) | function test_values_from_related_models_are_cached()

FILE: tests/Unit/Behaviours/ReadOnly/HasReadOnlyTest.php
  class HasReadOnlyTest (line 9) | final class HasReadOnlyTest extends TestCase
    method test_attributes_cannot_be_set (line 11) | function test_attributes_cannot_be_set()
    method test_model_cannot_be_saved (line 18) | function test_model_cannot_be_saved()

FILE: tests/Unit/Behaviours/SlugTest.php
  class SlugTest (line 7) | class SlugTest extends TestCase
    method test_random_slug_is_random (line 9) | public function test_random_slug_is_random()
    method test_slugs_are_8_characters_long (line 14) | public function test_slugs_are_8_characters_long()

FILE: tests/Unit/Database/Traits/HasCamelCasingTest.php
  class HasCamelCasingTest (line 10) | class HasCamelCasingTest extends TestCase
    method init (line 14) | public function init()
    method test_attributes_as_array (line 21) | public function test_attributes_as_array()
    method test_attribute_declaration (line 31) | public function test_attribute_declaration()
    method test_attribute_retrieval (line 38) | public function test_attribute_retrieval()
    method test_attribute_conversion (line 43) | public function test_attribute_conversion()
    method test_attribute_conversion_leaves_pivots (line 55) | public function test_attribute_conversion_leaves_pivots()
    method test_model_filling (line 67) | public function test_model_filling()
    method test_isset_unset (line 80) | public function test_isset_unset()
    method test_model_hidden_fields (line 104) | public function test_model_hidden_fields()
    method test_model_date_handling (line 123) | public function test_model_date_handling()

FILE: tests/Unit/Stubs/CountCache/Comment.php
  class Comment (line 7) | class Comment extends Model
    method countCaches (line 11) | public function countCaches()

FILE: tests/Unit/Stubs/CountCache/Post.php
  class Post (line 7) | class Post extends Model
    method countCaches (line 11) | public function countCaches()

FILE: tests/Unit/Stubs/CountCache/User.php
  class User (line 7) | class User extends Model
    method slugStrategy (line 11) | public function slugStrategy()

FILE: tests/Unit/Stubs/ModelStub.php
  class ModelStub (line 6) | class ModelStub extends ParentModelStub

FILE: tests/Unit/Stubs/ParentModelStub.php
  class ParentModelStub (line 4) | class ParentModelStub
    method attributesToArray (line 6) | public function attributesToArray()
    method getAttribute (line 11) | public function getAttribute($key)
    method setAttribute (line 16) | public function setAttribute($key, $value)
    method isRelation (line 21) | public function isRelation($key)

FILE: tests/Unit/Stubs/PivotModelStub.php
  class PivotModelStub (line 6) | class PivotModelStub extends ParentModelStub

FILE: tests/Unit/Stubs/ReadOnlyModelStub.php
  class ReadOnlyModelStub (line 8) | final class ReadOnlyModelStub extends Model

FILE: tests/Unit/Stubs/RealModelStub.php
  class RealModelStub (line 9) | class RealModelStub extends Model
    method fakeRelationship (line 23) | public function fakeRelationship()
    method countCaches (line 56) | public function countCaches()

FILE: tests/Unit/Stubs/SumCache/Item.php
  class Item (line 7) | class Item extends Model
    method sumCaches (line 11) | public function sumCaches()

FILE: tests/Unit/Stubs/SumCache/Order.php
  class Order (line 6) | class Order extends Model

FILE: tests/Unit/TestCase.php
  class TestCase (line 7) | class TestCase extends PHPUnit_Framework_TestCase
    method setUp (line 9) | public function setUp(): void
    method tearDown (line 16) | public function tearDown(): void
    method init (line 21) | public function init()
Condensed preview — 69 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (91K chars).
[
  {
    "path": ".github/workflows/test.yml",
    "chars": 656,
    "preview": "name: Test\n\non: ['push', 'pull_request']\n\njobs:\n  ci:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        "
  },
  {
    "path": ".gitignore",
    "chars": 95,
    "preview": "/vendor\ncomposer.phar\ncomposer.lock\n.phpunit.result.cache\n.DS_Store\n.idea*\n.php-cs-fixer.cache\n"
  },
  {
    "path": "LICENSE",
    "chars": 1060,
    "preview": "Copyright (c) 2014-2015 Kirk Bushell\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof th"
  },
  {
    "path": "README.md",
    "chars": 14378,
    "preview": "# Eloquence\n\n![Version](https://img.shields.io/packagist/v/kirkbushell/eloquence.svg)\n![Downloads](https://img.shields.i"
  },
  {
    "path": "composer.json",
    "chars": 2211,
    "preview": "{\n    \"name\": \"kirkbushell/eloquence\",\n    \"description\": \"A set of extensions adding additional functionality and consi"
  },
  {
    "path": "config/eloquence.php",
    "chars": 188,
    "preview": "<?php\n\nreturn [\n    'logging' => [\n        'enabled' => env('ELOQUENCE_LOGGING_ENABLED', false),\n        'driver' => env"
  },
  {
    "path": "phpunit.xml",
    "chars": 750,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit backupGlobals=\"false\"\n         backupStaticAttributes=\"false\"\n         b"
  },
  {
    "path": "src/Behaviours/CacheConfig.php",
    "chars": 1483,
    "preview": "<?php\n\nnamespace Eloquence\\Behaviours;\n\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Illuminate\\Database\\Eloquent\\Relatio"
  },
  {
    "path": "src/Behaviours/Cacheable.php",
    "chars": 3925,
    "preview": "<?php\n\nnamespace Eloquence\\Behaviours;\n\nuse Closure;\nuse Eloquence\\Behaviours\\SumCache\\SummedBy;\nuse Illuminate\\Database"
  },
  {
    "path": "src/Behaviours/CountCache/CountCache.php",
    "chars": 2601,
    "preview": "<?php\n\nnamespace Eloquence\\Behaviours\\CountCache;\n\nuse Eloquence\\Behaviours\\Cacheable;\nuse Eloquence\\Behaviours\\CacheCon"
  },
  {
    "path": "src/Behaviours/CountCache/CountedBy.php",
    "chars": 226,
    "preview": "<?php\n\nnamespace Eloquence\\Behaviours\\CountCache;\n\nuse Attribute;\n\n#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_"
  },
  {
    "path": "src/Behaviours/CountCache/HasCounts.php",
    "chars": 288,
    "preview": "<?php\n\nnamespace Eloquence\\Behaviours\\CountCache;\n\ntrait HasCounts\n{\n    public static function bootHasCounts(): void\n  "
  },
  {
    "path": "src/Behaviours/CountCache/Observer.php",
    "chars": 1104,
    "preview": "<?php\n\nnamespace Eloquence\\Behaviours\\CountCache;\n\n/**\n * The Observer is used for watching for model updates and making"
  },
  {
    "path": "src/Behaviours/HasCamelCasing.php",
    "chars": 5449,
    "preview": "<?php\n\nnamespace Eloquence\\Behaviours;\n\nuse Illuminate\\Support\\Str;\n\ntrait HasCamelCasing\n{\n    /**\n     * Alter eloquen"
  },
  {
    "path": "src/Behaviours/HasSlugs.php",
    "chars": 4086,
    "preview": "<?php\n\nnamespace Eloquence\\Behaviours;\n\nuse Eloquence\\Exceptions\\UnableToCreateSlugException;\nuse Illuminate\\Support\\Str"
  },
  {
    "path": "src/Behaviours/ReadOnly/HasReadOnly.php",
    "chars": 298,
    "preview": "<?php\n\nnamespace Eloquence\\Behaviours\\ReadOnly;\n\ntrait HasReadOnly\n{\n    public function setAttribute($key, $value)\n    "
  },
  {
    "path": "src/Behaviours/ReadOnly/WriteAccessDenied.php",
    "chars": 235,
    "preview": "<?php\n\nnamespace Eloquence\\Behaviours\\ReadOnly;\n\nfinal class WriteAccessDenied extends \\RuntimeException\n{\n    public fu"
  },
  {
    "path": "src/Behaviours/Slug.php",
    "chars": 2321,
    "preview": "<?php\n\nnamespace Eloquence\\Behaviours;\n\nuse Hashids\\Hashids;\nuse Illuminate\\Contracts\\Database\\Eloquent\\Castable;\nuse Il"
  },
  {
    "path": "src/Behaviours/SumCache/HasSums.php",
    "chars": 278,
    "preview": "<?php\n\nnamespace Eloquence\\Behaviours\\SumCache;\n\ntrait HasSums\n{\n    public static function bootHasSums(): void\n    {\n  "
  },
  {
    "path": "src/Behaviours/SumCache/Observer.php",
    "chars": 434,
    "preview": "<?php\n\nnamespace Eloquence\\Behaviours\\SumCache;\n\nclass Observer\n{\n    public function created($model)\n    {\n        SumC"
  },
  {
    "path": "src/Behaviours/SumCache/SumCache.php",
    "chars": 2722,
    "preview": "<?php\n\nnamespace Eloquence\\Behaviours\\SumCache;\n\nuse Eloquence\\Behaviours\\Cacheable;\nuse Eloquence\\Behaviours\\CacheConfi"
  },
  {
    "path": "src/Behaviours/SumCache/Summable.php",
    "chars": 854,
    "preview": "<?php\n\nnamespace Eloquence\\Behaviours\\SumCache;\n\ninterface Summable\n{\n    /**\n     * Returns a key->value array of the r"
  },
  {
    "path": "src/Behaviours/SumCache/SummedBy.php",
    "chars": 238,
    "preview": "<?php\n\nnamespace Eloquence\\Behaviours\\SumCache;\n\nuse Attribute;\n\n#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_RE"
  },
  {
    "path": "src/Behaviours/ValueCache/HasValues.php",
    "chars": 287,
    "preview": "<?php\n\nnamespace Eloquence\\Behaviours\\ValueCache;\n\ntrait HasValues\n{\n    public static function bootHasValues(): void\n  "
  },
  {
    "path": "src/Behaviours/ValueCache/Observer.php",
    "chars": 287,
    "preview": "<?php\n\nnamespace Eloquence\\Behaviours\\ValueCache;\n\nclass Observer\n{\n    public function created($model): void\n    {\n    "
  },
  {
    "path": "src/Behaviours/ValueCache/ValueCache.php",
    "chars": 1598,
    "preview": "<?php\n\nnamespace Eloquence\\Behaviours\\ValueCache;\n\nuse Eloquence\\Behaviours\\Cacheable;\nuse Eloquence\\Behaviours\\CacheCon"
  },
  {
    "path": "src/Behaviours/ValueCache/ValuedBy.php",
    "chars": 241,
    "preview": "<?php\n\nnamespace Eloquence\\Behaviours\\ValueCache;\n\nuse Attribute;\n\n#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_"
  },
  {
    "path": "src/Database/Model.php",
    "chars": 305,
    "preview": "<?php\n\nnamespace Eloquence\\Database;\n\nuse Eloquence\\Behaviours\\HasCamelCasing;\n\n/**\n * Class Model\n *\n * Have your model"
  },
  {
    "path": "src/EloquenceServiceProvider.php",
    "chars": 658,
    "preview": "<?php\n\nnamespace Eloquence;\n\nuse Eloquence\\Utilities\\DBQueryLog;\nuse Illuminate\\Support\\ServiceProvider;\n\nclass Eloquenc"
  },
  {
    "path": "src/Exceptions/UnableToCreateSlugException.php",
    "chars": 97,
    "preview": "<?php\n\nnamespace Eloquence\\Exceptions;\n\nclass UnableToCreateSlugException extends \\Exception\n{\n}\n"
  },
  {
    "path": "src/Utilities/DBQueryLog.php",
    "chars": 830,
    "preview": "<?php\n\nnamespace Eloquence\\Utilities;\n\nuse Illuminate\\Database\\Events\\QueryExecuted;\nuse Illuminate\\Support\\Facades\\DB;\n"
  },
  {
    "path": "src/Utilities/RebuildCaches.php",
    "chars": 3063,
    "preview": "<?php\n\nnamespace Eloquence\\Utilities;\n\nuse Eloquence\\Behaviours\\CountCache\\HasCounts;\nuse Eloquence\\Behaviours\\SumCache\\"
  },
  {
    "path": "tests/Acceptance/AcceptanceTestCase.php",
    "chars": 2824,
    "preview": "<?php\nnamespace Tests\\Acceptance;\n\nuse Eloquence\\EloquenceServiceProvider;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse"
  },
  {
    "path": "tests/Acceptance/ChainedAggregatesTest.php",
    "chars": 514,
    "preview": "<?php\n\nnamespace Tests\\Acceptance;\n\nuse Tests\\Acceptance\\Models\\Category;\nuse Tests\\Acceptance\\Models\\Comment;\n\nclass Ch"
  },
  {
    "path": "tests/Acceptance/CountCacheTest.php",
    "chars": 1987,
    "preview": "<?php\nnamespace Tests\\Acceptance;\n\nuse Tests\\Acceptance\\Models\\Comment;\nuse Tests\\Acceptance\\Models\\Post;\nuse Tests\\Acce"
  },
  {
    "path": "tests/Acceptance/GuardedColumnsTest.php",
    "chars": 420,
    "preview": "<?php\n\nnamespace Tests\\Acceptance;\n\nuse Tests\\Acceptance\\Models\\GuardedUser;\n\nclass GuardedColumnsTest extends Acceptanc"
  },
  {
    "path": "tests/Acceptance/HasSlugsTest.php",
    "chars": 568,
    "preview": "<?php\nnamespace Tests\\Acceptance;\n\nuse Tests\\Acceptance\\Models\\Post;\nuse Tests\\Acceptance\\Models\\User;\n\nclass HasSlugsTe"
  },
  {
    "path": "tests/Acceptance/Models/Category.php",
    "chars": 470,
    "preview": "<?php\n\nnamespace Tests\\Acceptance\\Models;\n\nuse Eloquence\\Behaviours\\HasCamelCasing;\nuse Illuminate\\Database\\Eloquent\\Fac"
  },
  {
    "path": "tests/Acceptance/Models/CategoryFactory.php",
    "chars": 242,
    "preview": "<?php\n\nnamespace Tests\\Acceptance\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Factories\\Factory;\n\nclass CategoryFactory ex"
  },
  {
    "path": "tests/Acceptance/Models/Comment.php",
    "chars": 946,
    "preview": "<?php\nnamespace Tests\\Acceptance\\Models;\n\nuse Eloquence\\Behaviours\\CountCache\\CountedBy;\nuse Eloquence\\Behaviours\\CountC"
  },
  {
    "path": "tests/Acceptance/Models/CommentFactory.php",
    "chars": 333,
    "preview": "<?php\n\nnamespace Tests\\Acceptance\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Factories\\Factory;\n\nclass CommentFactory ext"
  },
  {
    "path": "tests/Acceptance/Models/GuardedUser.php",
    "chars": 355,
    "preview": "<?php\nnamespace Tests\\Acceptance\\Models;\n\nuse Eloquence\\Behaviours\\HasCamelCasing;\nuse Illuminate\\Database\\Eloquent\\Mode"
  },
  {
    "path": "tests/Acceptance/Models/Item.php",
    "chars": 779,
    "preview": "<?php\nnamespace Tests\\Acceptance\\Models;\n\nuse Eloquence\\Behaviours\\SumCache\\HasSums;\nuse Eloquence\\Behaviours\\HasCamelCa"
  },
  {
    "path": "tests/Acceptance/Models/ItemFactory.php",
    "chars": 353,
    "preview": "<?php\n\nnamespace Tests\\Acceptance\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Factories\\Factory;\n\nclass ItemFactory extend"
  },
  {
    "path": "tests/Acceptance/Models/Order.php",
    "chars": 491,
    "preview": "<?php\nnamespace Tests\\Acceptance\\Models;\n\nuse Eloquence\\Behaviours\\HasCamelCasing;\nuse Illuminate\\Database\\Eloquent\\Fact"
  },
  {
    "path": "tests/Acceptance/Models/OrderFactory.php",
    "chars": 240,
    "preview": "<?php\n\nnamespace Tests\\Acceptance\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Factories\\Factory;\n\nclass OrderFactory exten"
  },
  {
    "path": "tests/Acceptance/Models/Post.php",
    "chars": 1384,
    "preview": "<?php\nnamespace Tests\\Acceptance\\Models;\n\nuse Eloquence\\Behaviours\\CountCache\\CountedBy;\nuse Eloquence\\Behaviours\\CountC"
  },
  {
    "path": "tests/Acceptance/Models/PostFactory.php",
    "chars": 346,
    "preview": "<?php\n\nnamespace Tests\\Acceptance\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Factories\\Factory;\n\nclass PostFactory extend"
  },
  {
    "path": "tests/Acceptance/Models/Role.php",
    "chars": 183,
    "preview": "<?php\n\nnamespace Tests\\Acceptance\\Models;\n\nuse Eloquence\\Behaviours\\HasCamelCasing;\nuse Illuminate\\Database\\Eloquent\\Mod"
  },
  {
    "path": "tests/Acceptance/Models/User.php",
    "chars": 750,
    "preview": "<?php\nnamespace Tests\\Acceptance\\Models;\n\nuse Eloquence\\Behaviours\\HasCamelCasing;\nuse Eloquence\\Behaviours\\HasSlugs;\nus"
  },
  {
    "path": "tests/Acceptance/Models/UserFactory.php",
    "chars": 341,
    "preview": "<?php\n\nnamespace Tests\\Acceptance\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Factories\\Factory;\n\nclass UserFactory extend"
  },
  {
    "path": "tests/Acceptance/RebuildCacheTest.php",
    "chars": 1151,
    "preview": "<?php\n\nnamespace Tests\\Acceptance;\n\nuse Tests\\Acceptance\\Models\\Item;\nuse Tests\\Acceptance\\Models\\Order;\nuse Tests\\Accep"
  },
  {
    "path": "tests/Acceptance/RebuildCachesCommandTest.php",
    "chars": 1326,
    "preview": "<?php\n\nnamespace Tests\\Acceptance;\n\nuse Tests\\Acceptance\\Models\\Item;\nuse Tests\\Acceptance\\Models\\Order;\nuse Tests\\Accep"
  },
  {
    "path": "tests/Acceptance/SumCacheTest.php",
    "chars": 2407,
    "preview": "<?php\nnamespace Tests\\Acceptance;\n\nuse Tests\\Acceptance\\Models\\Item;\nuse Tests\\Acceptance\\Models\\Order;\n\nclass SumCacheT"
  },
  {
    "path": "tests/Acceptance/ValueCacheTest.php",
    "chars": 550,
    "preview": "<?php\n\nnamespace Tests\\Acceptance;\n\nuse Tests\\Acceptance\\Models\\Category;\nuse Tests\\Acceptance\\Models\\Post;\n\nclass Value"
  },
  {
    "path": "tests/Unit/Behaviours/ReadOnly/HasReadOnlyTest.php",
    "chars": 558,
    "preview": "<?php\n\nnamespace Tests\\Unit\\Behaviours\\ReadOnly;\n\nuse Eloquence\\Behaviours\\ReadOnly\\WriteAccessDenied;\nuse Tests\\Unit\\St"
  },
  {
    "path": "tests/Unit/Behaviours/SlugTest.php",
    "chars": 396,
    "preview": "<?php\nnamespace tests\\Unit\\Database\\Traits;\n\nuse Eloquence\\Behaviours\\Slug;\nuse Tests\\Unit\\TestCase;\n\nclass SlugTest ext"
  },
  {
    "path": "tests/Unit/Database/Traits/HasCamelCasingTest.php",
    "chars": 3828,
    "preview": "<?php\nnamespace tests\\Unit\\Database\\Traits;\n\nuse Carbon\\Carbon;\nuse Tests\\Unit\\Stubs\\ModelStub;\nuse Tests\\Unit\\Stubs\\Piv"
  },
  {
    "path": "tests/Unit/Stubs/CountCache/Comment.php",
    "chars": 363,
    "preview": "<?php\nnamespace Tests\\Unit\\Stubs\\CountCache;\n\nuse Eloquence\\Behaviours\\CountCache\\HasCounts;\nuse Eloquence\\Database\\Mode"
  },
  {
    "path": "tests/Unit/Stubs/CountCache/Post.php",
    "chars": 551,
    "preview": "<?php\nnamespace Tests\\Unit\\Stubs\\CountCache;\n\nuse Eloquence\\Behaviours\\CountCache\\HasCounts;\nuse Eloquence\\Database\\Mode"
  },
  {
    "path": "tests/Unit/Stubs/CountCache/User.php",
    "chars": 240,
    "preview": "<?php\nnamespace Tests\\Unit\\Stubs\\CountCache;\n\nuse Eloquence\\Behaviours\\HasSlugs;\nuse Illuminate\\Database\\Eloquent\\Model;"
  },
  {
    "path": "tests/Unit/Stubs/ModelStub.php",
    "chars": 343,
    "preview": "<?php\nnamespace Tests\\Unit\\Stubs;\n\nuse Eloquence\\Behaviours\\HasCamelCasing;\n\nclass ModelStub extends ParentModelStub\n{\n "
  },
  {
    "path": "tests/Unit/Stubs/ParentModelStub.php",
    "chars": 412,
    "preview": "<?php\nnamespace Tests\\Unit\\Stubs;\n\nclass ParentModelStub\n{\n    public function attributesToArray()\n    {\n        return "
  },
  {
    "path": "tests/Unit/Stubs/PivotModelStub.php",
    "chars": 278,
    "preview": "<?php\nnamespace Tests\\Unit\\Stubs;\n\nuse Eloquence\\Behaviours\\HasCamelCasing;\n\nclass PivotModelStub extends ParentModelStu"
  },
  {
    "path": "tests/Unit/Stubs/ReadOnlyModelStub.php",
    "chars": 244,
    "preview": "<?php\n\nnamespace Tests\\Unit\\Stubs;\n\nuse Eloquence\\Behaviours\\ReadOnly\\HasReadOnly;\nuse Illuminate\\Database\\Eloquent\\Mode"
  },
  {
    "path": "tests/Unit/Stubs/RealModelStub.php",
    "chars": 2088,
    "preview": "<?php\nnamespace Tests\\Unit\\Stubs;\n\nuse Eloquence\\Behaviours\\CountCache\\CacheConfig;\nuse Eloquence\\Behaviours\\CountCache\\"
  },
  {
    "path": "tests/Unit/Stubs/SumCache/Item.php",
    "chars": 540,
    "preview": "<?php\nnamespace Tests\\Unit\\Stubs\\SumCache;\n\nuse Eloquence\\Behaviours\\SumCache\\HasSums;\nuse Eloquence\\Database\\Model;\n\ncl"
  },
  {
    "path": "tests/Unit/Stubs/SumCache/Order.php",
    "chars": 115,
    "preview": "<?php\nnamespace Tests\\Unit\\Stubs\\SumCache;\n\nuse Illuminate\\Database\\Eloquent\\Model;\n\nclass Order extends Model\n{\n}\n"
  },
  {
    "path": "tests/Unit/TestCase.php",
    "chars": 423,
    "preview": "<?php\nnamespace Tests\\Unit;\n\nuse PHPUnit\\Framework\\TestCase as PHPUnit_Framework_TestCase;\nuse Mockery as m;\n\nclass Test"
  }
]

About this extraction

This page contains the full source code of the kirkbushell/eloquence GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 69 files (80.6 KB), approximately 21.6k tokens, and a symbol index with 229 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!