diff --git a/CHANGELOG.md b/CHANGELOG.md
index a150d45..ce0d926 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
## Unreleased
+- The configuration values a request read are answered from a request memo on the second read, under the `config_memo` switch: no scope resolution and no type lookup per read.
+
+## Unreleased
+
- The stock id of the current website as a cache entry in the local files, from the new FastBootInventory module: no sales channel select per request. A change of a stock's sales channels cleans it.
## 0.2.0-rc8
diff --git a/dev/README.md b/dev/README.md
index 4dd92de..638a8e8 100644
--- a/dev/README.md
+++ b/dev/README.md
@@ -30,6 +30,7 @@ Production installs use the default-enabled optimizations. These switches suppor
| `tax_rates` | Tax rates and applied rates per rate request from the cache, for requests that are not customer-specific. | Yes |
| `customer_groups` | Customer groups by id from the cache, with their excluded websites. | Yes |
| `currency_rates` | Currency rates from the cache; a rate import cleans them. | Yes |
+| `config_memo` | The configuration values a request read, by scope, scope code and path; a clean, a mutable set and a store switch empty it. | No |
| `stock_id` | The stock id of the current website from the cache; a change of a stock's sales channels cleans it. | Yes |
These switches do not enable PHP class preload; that requires `opcache.preload` at FPM startup.
diff --git a/src/FastBoot/Plugin/Config/ForgetValueMemo.php b/src/FastBoot/Plugin/Config/ForgetValueMemo.php
new file mode 100644
index 0000000..6391953
--- /dev/null
+++ b/src/FastBoot/Plugin/Config/ForgetValueMemo.php
@@ -0,0 +1,42 @@
+memo->forget();
+
+ return $result;
+ }
+
+ /**
+ * @param object $subject
+ * @param mixed $result
+ * @return mixed
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function afterSetCurrentStore(object $subject, $result)
+ {
+ $this->memo->forget();
+
+ return $result;
+ }
+}
diff --git a/src/FastBoot/Plugin/Config/ValueMemo.php b/src/FastBoot/Plugin/Config/ValueMemo.php
new file mode 100644
index 0000000..dc2ddb6
--- /dev/null
+++ b/src/FastBoot/Plugin/Config/ValueMemo.php
@@ -0,0 +1,66 @@
+ */
+ private array $values = [];
+
+ public function __construct(
+ private readonly Feature $feature,
+ ) {
+ }
+
+ /**
+ * @param mixed $path
+ * @param mixed $scope
+ * @param mixed $scopeCode
+ * @return mixed
+ */
+ public function aroundGetValue(Config $subject, callable $proceed, $path = null, $scope = 'default', $scopeCode = null)
+ {
+ if (($scopeCode !== null && !is_scalar($scopeCode)) || !$this->feature->on(self::SWITCH)) {
+ return $proceed($path, $scope, $scopeCode);
+ }
+ $key = $scope . "\0" . ($scopeCode ?? '') . "\0" . $path;
+ if (!array_key_exists($key, $this->values)) {
+ $this->values[$key] = $proceed($path, $scope, $scopeCode);
+ }
+
+ return $this->values[$key];
+ }
+
+ /**
+ * @param mixed $result
+ * @return mixed
+ */
+ public function afterClean(Config $subject, $result)
+ {
+ $this->values = [];
+
+ return $result;
+ }
+
+ public function forget(): void
+ {
+ $this->values = [];
+ }
+
+ public function _resetState(): void
+ {
+ $this->values = [];
+ }
+}
diff --git a/src/FastBoot/Test/Unit/Plugin/Config/ValueMemoTest.php b/src/FastBoot/Test/Unit/Plugin/Config/ValueMemoTest.php
new file mode 100644
index 0000000..da8fb76
--- /dev/null
+++ b/src/FastBoot/Test/Unit/Plugin/Config/ValueMemoTest.php
@@ -0,0 +1,82 @@
+reads = 0;
+ $feature = $this->createStub(Feature::class);
+ $feature->method('on')->willReturn(true);
+
+ return new ValueMemo($feature);
+ }
+
+ private function read(ValueMemo $memo, string $path, string $scope = 'default', mixed $code = null): mixed
+ {
+ return $memo->aroundGetValue($this->createStub(Config::class), function ($path) {
+ $this->reads++;
+
+ return $path === 'a/null' ? null : strtoupper($path);
+ }, $path, $scope, $code);
+ }
+
+ public function testASecondReadOfTheSameValueSkipsTheConfiguration(): void
+ {
+ $memo = $this->memo();
+
+ $this->assertSame('A/B', $this->read($memo, 'a/b', 'store'));
+ $this->assertSame('A/B', $this->read($memo, 'a/b', 'store'));
+ $this->assertNull($this->read($memo, 'a/null'));
+ $this->assertNull($this->read($memo, 'a/null'));
+ $this->assertSame(2, $this->reads);
+ }
+
+ public function testTheScopeAndTheCodeAreInTheKey(): void
+ {
+ $memo = $this->memo();
+
+ $this->read($memo, 'a/b', 'store');
+ $this->read($memo, 'a/b', 'store', 'second');
+ $this->read($memo, 'a/b', 'website');
+
+ $this->assertSame(3, $this->reads);
+ }
+
+ public function testACleanAMutableSetAndAStoreSwitchEmptyTheMemo(): void
+ {
+ $memo = $this->memo();
+ $forget = new ForgetValueMemo($memo);
+
+ $this->read($memo, 'a/b');
+ $memo->afterClean($this->createStub(Config::class), null);
+ $this->read($memo, 'a/b');
+ $forget->afterSetValue($this->createStub(MutableScopeConfigInterface::class), null);
+ $this->read($memo, 'a/b');
+ $forget->afterSetCurrentStore(new \stdClass(), null);
+ $this->read($memo, 'a/b');
+
+ $this->assertSame(4, $this->reads);
+ }
+
+ public function testAScopeObjectAsCodeIsNotMemoised(): void
+ {
+ $memo = $this->memo();
+
+ $this->read($memo, 'a/b', 'store', new \stdClass());
+ $this->read($memo, 'a/b', 'store', new \stdClass());
+
+ $this->assertSame(2, $this->reads);
+ }
+}
diff --git a/src/FastBoot/etc/di.xml b/src/FastBoot/etc/di.xml
index 0bf2428..130c3f6 100644
--- a/src/FastBoot/etc/di.xml
+++ b/src/FastBoot/etc/di.xml
@@ -22,6 +22,17 @@
+
+
+
+
+
+
+
+
+
+
+