Skip to content

Commit

Permalink
Added a in-memory classmap option (#8)
Browse files Browse the repository at this point in the history
* Added a in-memory classmap option

* Updated the expected exception
  • Loading branch information
BelleNottelling committed Nov 20, 2023
1 parent ce5aeeb commit fcf0f55
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 24 deletions.
40 changes: 25 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# AntLoader


[![Packagist Downloads](https://img.shields.io/packagist/dt/antcms/antloader)](https://packagist.org/packages/antcms/antloader)
[![PHP Tests](https://github.com/AntCMS-org/AntLoader/actions/workflows/tests.yml/badge.svg)](https://github.com/AntCMS-org/AntLoader/actions/workflows/tests.yml)
[![PHPStan](https://github.com/AntCMS-org/AntLoader/actions/workflows/phpstan.yml/badge.svg)](https://github.com/AntCMS-org/AntLoader/actions/workflows/phpstan.yml)
Expand Down Expand Up @@ -36,13 +35,14 @@ $loader->resetClassMap(); // Reset the classmap, clearing the existing one out f
```

## Configuration

AntLoader accepts an array to configure it's available options.
None of the configuration options are required, however at a minimum it is recommended to specify a path unless you know APCu will be usable in all environments for your application.
**Note**: Please read the "Classmap Caching Options" section of this document, as that covers the strengths and weaknesses of each caching approach.

```PHP
$config = [
'mode' => 'auto', // Can be 'auto', 'filesystem', 'apcu', or 'none'.
'mode' => 'auto', // Can be 'auto', 'filesystem', 'apcu', 'memory', or 'none'.
'path' => '/path/to/save/classmap.php', // Where should AntLoader store the classmap if the file system cache option is used.
'key' => 'customApcuKey', // The APCu key used when storing the classmap. This does not usually need to be overridden.
'ttl' => 3600, // Allows you to set the time to live when using APCu. Value is in seconds.
Expand All @@ -54,6 +54,7 @@ $loader = new AntCMS\AntLoader($config);
If you are looking to build an application that is fairly portable, we recommend configuring the path and nothing else.
This configuration will allow AntLoader to use APCu when available and fallback to the filesystem when it is not.
Providing a specific path for the file system cache ensures that the classmap will be stored in a location that is persistent.

```PHP
$config = [
'path' => __DIR__ . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . 'classMap.php', // Tells AntLoader to store the classmap in a sub-folder named "cache".
Expand All @@ -64,42 +65,51 @@ $loader = new AntCMS\AntLoader($config);
## Classmap Caching Options

### APCu

Starting from version 2.0.0, AntLoader now supports storing the Classmap in RAM using APCu.
This feature allows AntLoader to achieve optimal performance by persisting the classmap between sessions.
Here are a few things to note about the APCu mode:

- AntLoader generates a random key based on the directory it resides in. This ensures a unique key name to avoid accidentally overwriting APCu keys. The generated key remains static throughout the lifespan of the application.
- As long as you aren't running two separate PHP applications & using the same copy of AntLoader (which you shouldn't be), this is sufficient to prevent issues.
- Depending on your web server configuration, using APCu may allow the classmap to be accessed by other PHP applications. However, in the case of AntLoader, this information only includes the namespaces/classes within your application and their respective paths.
- By default, AntLoader stores the classmap with APCu using a Time-to-Live (TTL) of 7 days.
- AntLoader generates a random key based on the directory it resides in. This ensures a unique key name to avoid accidentally overwriting APCu keys. The generated key remains static throughout the lifespan of the application.
- As long as you aren't running two separate PHP applications & using the same copy of AntLoader (which you shouldn't be), this is sufficient to prevent issues.
- Depending on your web server configuration, using APCu may allow the classmap to be accessed by other PHP applications. However, in the case of AntLoader, this information only includes the namespaces/classes within your application and their respective paths.
- By default, AntLoader stores the classmap with APCu using a Time-to-Live (TTL) of 7 days.

### Filesystem

The filesystem caching method, in theory, is slower than the APCu caching method.
However, the actual performance can vary based on external variables, and on well-performing systems with minimal disk load, the difference is likely to be minimal.
Here are some details about the filesystem caching method:

- By default, AntLoader saves the classmap to the system's temporary directory, which may not survive through multiple sessions. It is recommended to override the default path and specify a more persistent location.
- The classmap file stored in the filesystem has no lifespan limit imposed by AntLoader. It will be retained until either you delete the file or call the `resetClassMap` function.
- Clearing or resetting the classmap is generally easier to perform outside of calling the `resetClassMap` function provided by AntLoader.
- By default, AntLoader saves the classmap to the system's temporary directory, which may not survive through multiple sessions. It is recommended to override the default path and specify a more persistent location.
- The classmap file stored in the filesystem has no lifespan limit imposed by AntLoader. It will be retained until either you delete the file or call the `resetClassMap` function.
- Clearing or resetting the classmap is generally easier to perform outside of calling the `resetClassMap` function provided by AntLoader.

### Memory

If you use the `memory` caching option, this will cause AntLoader to still create a classmap and then load it, however the generated classmap will not be saved anywhere. Typically generating a classmap can be quite slow so this is likely the worst performing option (even compared to no class map), however it may be useful in testing or if you need to point the autoloader to a directory you don't have control over, as the `composer/class-map-generator` package generates a classmap regardless of PSR0 / PSR4 & don't wish to cache the result from that.

## Notes

### Performance
- While it's not strictly necessary to use the classmap functionality, we strongly recommend doing so for optimal performance.
- Testing shows that it reduces the time to find and load `1000` random classes by `95%` (from `0.0699` seconds to `0.0037` seconds)
- Depending on the setup, prepending AntLoader may speed up the performance of your application.
- For example, if you are using composer's autoloader and have a lot of composer packages, that may delay the time it takes to load classes within your application.
- In this example, the classes inside of your application will load slightly faster and classes loaded through composer will be slightly slower.
- Potential improvements are highly dependent on the specific application and environment. In most situations, the difference is likely to be very minimal.

- While it's not strictly necessary to use the classmap functionality, we strongly recommend doing so for optimal performance.
- Testing shows that it reduces the time to find and load `1000` random classes by `95%` (from `0.0699` seconds to `0.0037` seconds)
- Depending on the setup, prepending AntLoader may speed up the performance of your application.
- For example, if you are using composer's autoloader and have a lot of composer packages, that may delay the time it takes to load classes within your application.
- In this example, the classes inside of your application will load slightly faster and classes loaded through composer will be slightly slower.
- Potential improvements are highly dependent on the specific application and environment. In most situations, the difference is likely to be very minimal.

So, we encourage you to take advantage of the classmap feature to get the best performance out of your application.

### Maintaining AntLoader

AntLoader is generally hands-off, except that we highly recommend clearing out / resetting the classmap after updating your application.
AntLoader will **never** remove outdated classes / paths from the classmap, so never allowing it to be rebuilt can negatively affect the performance of your application if classes are renamed or moved.
The best way to do this is simply to call the `resetClassMap` function that AntLoader provides. This will automatically reset the classmap for the current Cache method.

#### Pruning the classmap

If you have an application where the class list may periodically change, you can prune the classmap periodically to ensure it's not filling up with classes that no longer exist.
To do so, simply call `pruneClassmap`. This function will return the number of pruned classes.

Expand Down
21 changes: 15 additions & 6 deletions src/AntLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,16 @@ class AntLoader
private bool $stopIfNotFound = false;

const noCache = 0;
const fileCache = 1;
const apcuCache = 2;
const inMemory = 1;
const fileCache = 2;
const apcuCache = 3;

/**
* Creates a new instance of AntLoader.
*
* @param array{mode?:string,path?:string,key?:string,ttl?:int,stopIfNotFound?:bool} $config (optional) Configuration options for AntLoader.
* Available keys:
* - 'mode': What mode to use for storing the classmap. Can be 'auto', 'filesystem', 'apcu', or 'none'.
* - 'mode': What mode to use for storing the classmap. Can be 'auto', 'filesystem', 'apcu', 'memory', or 'none'.
* - 'path': Where to save the classmap to. By default, this will be saved to a random temp file.
* If you are using the file system cache, it is recommended to manually specify this path to one that is persistent between sessions.
* - 'key': Use this option to override the unique key that AntLoader uses with its cache.
Expand Down Expand Up @@ -71,22 +72,25 @@ public function __construct(array $config = [])
],
'auto' => [
'type' => extension_loaded('apcu') && apcu_enabled() ? self::apcuCache : self::fileCache,
'key' => $generatedID
'key' => $generatedID
],
'filesystem' => [
'type' => self::fileCache
],
'apcu' => [
'type' => self::apcuCache,
'key' => $generatedID
'key' => $generatedID
],
'memory' => [
'type' => self::inMemory,
]
];

if (array_key_exists($config['mode'], $cacheOptions)) {
$this->cacheType = intval($cacheOptions[$config['mode']]['type']);
$this->cacheKey = strval($cacheOptions[$config['mode']]['key'] ?? '');
} else {
throw new \Exception("Unsupported cache mode. Please ensure you are specifying 'auto', 'filesystem', 'apcu', or 'none'.");
throw new \Exception("Unsupported cache mode. Please ensure you are specifying 'auto', 'filesystem', 'apcu', 'memory', or 'none'.");
}

$this->cacheTtl = $config['ttl'];
Expand All @@ -100,6 +104,10 @@ public function __construct(array $config = [])
public function checkClassMap(): void
{
switch ($this->cacheType) {
case self::inMemory:
$classMap = $this->generateMap();
$this->classMap = $classMap->getMap();
return;
case self::noCache:
return;
case self::fileCache:
Expand Down Expand Up @@ -299,6 +307,7 @@ private function getFile(string $class, string $path, string $namespace): string
private function saveMap(): void
{
switch ($this->cacheType) {
case self::inMemory:
case self::noCache:
return;
case self::fileCache:
Expand Down
2 changes: 1 addition & 1 deletion tests/ExceptionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@

it('Throws unknown caching type', function () {
$loader = new \AntCMS\AntLoader(['mode' => 'invalid']);
})->throws(Exception::class, "Unsupported cache mode. Please ensure you are specifying 'auto', 'filesystem', 'apcu', or 'none'.");
})->throws(Exception::class, "Unsupported cache mode. Please ensure you are specifying 'auto', 'filesystem', 'apcu', 'memory', or 'none'.");
4 changes: 2 additions & 2 deletions tests/LoaderTest.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

test('PSR4Loader', function () {
$cacheModes = ['none', 'auto', 'filesystem', 'apcu'];
$cacheModes = ['none', 'auto', 'filesystem', 'apcu', 'memory'];
$psr4Classes = ['Class1', 'Class2', 'Namespace1\Class1'];

foreach ($cacheModes as $mode) {
Expand All @@ -16,7 +16,7 @@
});

test('PSR0Loader', function () {
$cacheModes = ['none', 'auto', 'filesystem', 'apcu'];
$cacheModes = ['none', 'auto', 'filesystem', 'apcu', 'memory'];
$psr4Classes = ['Test_Class1'];

foreach ($cacheModes as $mode) {
Expand Down

0 comments on commit fcf0f55

Please sign in to comment.