• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

adriansuter / php-autoload-override / 24770212636

22 Apr 2026 09:12AM UTC coverage: 90.385% (-2.9%) from 93.235%
24770212636

push

github

web-flow
Merge pull request #57 from adriansuter/mock

mock

39 of 40 new or added lines in 5 files covered. (97.5%)

24 existing lines in 1 file now uncovered.

329 of 364 relevant lines covered (90.38%)

5.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.24
/src/MockRegistry.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace AdrianSuter\Autoload\Override;
6

7
/**
8
 * Registry for mock values used in Override closures.
9
 *
10
 * Replaces the $GLOBALS anti-pattern with a scoped, type-safe alternative.
11
 * Per-class overrides take priority over global fallbacks.
12
 *
13
 * Usage in bootstrap:
14
 *
15
 *   Override::apply($classLoader, [
16
 *       Clock::class => [
17
 *           'time' => function (): int {
18
 *               return MockRegistry::get(Clock::class, 'time', \time());
19
 *           }
20
 *       ]
21
 *   ]);
22
 *
23
 * Usage in test:
24
 *
25
 *   MockRegistry::set(Clock::class, 'time', 1574333284);
26
 *   // ...assert...
27
 *
28
 *   // in tearDown():
29
 *   MockRegistry::reset(Clock::class);
30
 */
31
final class MockRegistry
32
{
33
    /** @var array<string, mixed> */
34
    private static array $global = [];
35

36
    /** @var array<string, array<string, mixed>> */
37
    private static array $perClass = [];
38

39
    /**
40
     * Set a per-class override value.
41
     *
42
     * @param class-string $className
43
     */
44
    public static function set(string $className, string $functionName, mixed $value): void
45
    {
46
        self::$perClass[$className][$functionName] = $value;
16✔
47
    }
48

49
    /**
50
     * Set a global fallback override (applies to all classes).
51
     */
52
    public static function setGlobal(string $functionName, mixed $value): void
53
    {
54
        self::$global[$functionName] = $value;
10✔
55
    }
56

57
    /**
58
     * Get an override value.
59
     *
60
     * Resolution order: per-class → global → $default.
61
     *
62
     * Note: $default is evaluated eagerly. Use has() + get() separately
63
     * if the fallback callable has side effects (e.g. an actual \rand() call).
64
     *
65
     * @param class-string $className
66
     */
67
    public static function get(string $className, string $functionName, mixed $default = null): mixed
68
    {
69
        return self::$perClass[$className][$functionName]
15✔
70
            ?? self::$global[$functionName]
15✔
71
            ?? $default;
15✔
72
    }
73

74
    /**
75
     * Check whether an override exists for the given class and function name.
76
     *
77
     * Checks per-class registry first, then global. Use this before get()
78
     * when the fallback has side effects.
79
     *
80
     * @param class-string $className
81
     */
82
    public static function has(string $className, string $functionName): bool
83
    {
84
        return array_key_exists($functionName, self::$perClass[$className] ?? [])
16✔
85
            || array_key_exists($functionName, self::$global);
16✔
86
    }
87

88
    /**
89
     * Reset overrides.
90
     *
91
     * Without argument: resets everything (per-class and global).
92
     * With class name:  resets only overrides for that class.
93
     *
94
     * @param class-string|null $className
95
     */
96
    public static function reset(?string $className = null): void
97
    {
98
        if ($className === null) {
26✔
99
            self::$global = [];
26✔
100
            self::$perClass = [];
26✔
101
        } else {
102
            unset(self::$perClass[$className]);
4✔
103
        }
104
    }
105

106
    /**
107
     * Reset only global overrides, leaving all per-class overrides intact.
108
     */
109
    public static function resetGlobal(): void
110
    {
111
        self::$global = [];
2✔
112
    }
113

114
    /**
115
     * Generate Override-ready closures for a class, backed by MockRegistry.
116
     *
117
     * Each closure uses the lazy pattern: the fallback is only invoked when
118
     * no MockRegistry override is set for that class and function name.
119
     * Pass real implementations as first-class callables:
120
     *
121
     *   MockRegistry::closures(Planet::class, [
122
     *       'time' => \time(...),
123
     *       'rand' => \rand(...),
124
     *   ])
125
     *
126
     * Typically called internally by OverrideFactory — use that for bootstrap
127
     * scripts; call this directly only when you need the closures array without
128
     * the full builder API.
129
     *
130
     * @param class-string $className
131
     * @param array<string, callable> $fallbacks function name => real implementation
132
     * @return array<string, \Closure>
133
     */
134
    public static function closures(string $className, array $fallbacks): array
135
    {
136
        $closures = [];
7✔
137
        foreach ($fallbacks as $functionName => $fallback) {
7✔
138
            $closures[$functionName] = static function () use ($className, $functionName, $fallback) {
7✔
139
                if (MockRegistry::has($className, $functionName)) {
7✔
140
                    return MockRegistry::get($className, $functionName);
5✔
141
                }
142
                return $fallback(...func_get_args());
3✔
143
            };
7✔
144
        }
145
        return $closures;
7✔
146
    }
147

148
    private function __construct()
149
    {
NEW
150
    }
×
151
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc