/var/www/html/wp-content/plugins/woocommerce/src/Internal/DependencyManagement/RuntimeContainer.php


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
<?php

declare( strict_types=);

namespace 
Automattic\WooCommerce\Internal\DependencyManagement;

use 
Automattic\WooCommerce\Blocks\Package as BlocksPackage;
use 
Automattic\WooCommerce\StoreApi\StoreApi;
use 
Automattic\WooCommerce\Utilities\StringUtil;

/**
 * Dependency injection container used at runtime.
 *
 * This is a simple container that doesn't implement explicit class registration.
 * Instead, all the classes in the Automattic\WooCommerce namespace can be resolved
 * and are considered as implicitly registered as single-instance classes
 * (so each class will be instantiated only once and the instance will be cached).
 */
class RuntimeContainer {
    
/**
     * The root namespace of all WooCommerce classes in the `src` directory.
     *
     * @var string
     */
    
const WOOCOMMERCE_NAMESPACE 'Automattic\\WooCommerce\\';

    
/**
     * Cache of classes already resolved.
     *
     * @var array
     */
    
protected array $resolved_cache;

    
/**
     * A copy of the initial resolved classes cache passed to the constructor.
     *
     * @var array
     */
    
protected array $initial_resolved_cache;

    
/**
     * Initializes a new instance of the class.
     *
     * @param array $initial_resolved_cache Dictionary of class name => instance, to be used as the starting point for the resolved classes cache.
     */
    
public function __construct( array $initial_resolved_cache ) {
        
$this->initial_resolved_cache $initial_resolved_cache;
        
$this->resolved_cache         $initial_resolved_cache;
    }

    
/**
     * Get an instance of a class.
     *
     * ContainerException will be thrown in these cases:
     *
     * - $class_name is outside the WooCommerce root namespace (and wasn't included in the initial resolve cache).
     * - The class referred by $class_name doesn't exist.
     * - Recursive resolution condition found.
     * - Reflection exception thrown when instantiating or initializing the class.
     *
     * A "recursive resolution condition" happens when class A depends on class B and at the same time class B depends on class A, directly or indirectly;
     * without proper handling this would lead to an infinite loop.
     *
     * Note that this method throwing ContainerException implies that code fixes are needed, it's not an error condition that's recoverable at runtime.
     *
     * @param string $class_name The class name.
     *
     * @return object The instance of the requested class.
     * @throws ContainerException Error when resolving the class to an object instance.
     * @throws \Exception Exception thrown in the constructor or in the 'init' method of one of the resolved classes.
     */
    
public function getstring $class_name ) {
        
$class_name    trim$class_name'\\' );
        
$resolve_chain = array();
        return 
$this->get_core$class_name$resolve_chain );
    }

    
/**
     * Core function to get an instance of a class.
     *
     * @param string $class_name The class name.
     * @param array  $resolve_chain Classes already resolved in this resolution chain. Passed between recursive calls to the method in order to detect a recursive resolution condition.
     * @return object The resolved object.
     * @throws ContainerException Error when resolving the class to an object instance.
     */
    
protected function get_corestring $class_name, array &$resolve_chain ) {
        if ( isset( 
$this->resolved_cache$class_name ] ) ) {
            return 
$this->resolved_cache$class_name ];
        }

        
// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped

        
if ( in_array$class_name$resolve_chaintrue ) ) {
            throw new 
ContainerException"Recursive resolution of class '$class_name'. Resolution chain: " implode', '$resolve_chain ) );
        }

        if ( ! 
$this->is_class_allowed$class_name ) ) {
            throw new 
ContainerException"Attempt to get an instance of class '$class_name', which is not in the " self::WOOCOMMERCE_NAMESPACE ' namespace. Did you forget to add a namespace import?' );
        }

        if ( ! 
class_exists$class_name ) ) {
            throw new 
ContainerException"Attempt to get an instance of class '$class_name', which doesn't exist." );
        }

        
// Account for the containers used by the Store API and Blocks.
        
if ( StringUtil::starts_with$class_name'Automattic\WooCommerce\StoreApi\\' ) ) {
            return 
StoreApi::container()->get$class_name );
        }
        if ( 
StringUtil::starts_with$class_name'Automattic\WooCommerce\Blocks\\' ) ) {
            return 
BlocksPackage::container()->get$class_name );
        }

        
$resolve_chain[] = $class_name;

        try {
            
$instance $this->instantiate_class_using_reflection$class_name$resolve_chain );
        } catch ( 
\ReflectionException $e ) {
            throw new 
ContainerException"Reflection error when resolving '$class_name': (" get_class$e ) . ") {$e->getMessage()}"0$e );
        }

        
// phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped

        
$this->resolved_cache$class_name ] = $instance;

        return 
$instance;
    }

    
/**
     * Get an instance of a class using reflection.
     * This method recursively calls 'get_core' (which in turn calls this method) for each of the arguments
     * in the 'init' method of the resolved class (if the method is public and non-static).
     *
     * @param string $class_name The name of the class to resolve.
     * @param array  $resolve_chain Classes already resolved in this resolution chain. Passed between recursive calls to the method in order to detect a recursive resolution condition.
     * @return object The resolved object.
     *
     * @throws ContainerException The 'init' method has invalid arguments.
     * @throws \ReflectionException Something went wrong when using reflection to get information about the class to resolve.
     */
    
private function instantiate_class_using_reflectionstring $class_name, array &$resolve_chain ): object {
        
$ref_class = new \ReflectionClass$class_name );
        
$instance  $ref_class->newInstance();
        if ( ! 
$ref_class->hasMethod'init' ) ) {
            return 
$instance;
        }

        
$init_method $ref_class->getMethod'init' );
        if ( ! 
$init_method->isPublic() || $init_method->isStatic() ) {
            return 
$instance;
        }

        
// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped

        
$init_args          $init_method->getParameters();
        
$init_arg_instances array_map(
            function ( 
\ReflectionParameter $arg ) use ( $class_name, &$resolve_chain ) {
                
$arg_type $arg->getType();
                if ( ! ( 
$arg_type instanceof \ReflectionNamedType ) ) {
                    throw new 
ContainerException"Error resolving '$class_name': argument '\${$arg->getName()}' doesn't have a type declaration." );
                }
                if ( 
$arg_type->isBuiltin() ) {
                    throw new 
ContainerException"Error resolving '$class_name': argument '\${$arg->getName()}' is not of a class type." );
                }
                if ( 
$arg->isPassedByReference() ) {
                    throw new 
ContainerException"Error resolving '$class_name': argument '\${$arg->getName()}' is passed by reference." );
                }
                return 
$this->get_core$arg_type->getName(), $resolve_chain );
            },
            
$init_args
        
);

        
// phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped

        
$init_method->invoke$instance, ...$init_arg_instances );

        return 
$instance;
    }

    
/**
     * Tells if the 'get' method can be used to resolve a given class.
     *
     * @param string $class_name The class name.
     * @return bool True if the class with the supplied name can be resolved with 'get'.
     */
    
public function hasstring $class_name ): bool {
        
$class_name trim$class_name'\\' );
        return 
$this->is_class_allowed$class_name ) || isset( $this->resolved_cache$class_name ] );
    }

    
/**
     * Checks to see whether a class is allowed to be registered.
     *
     * @param string $class_name The class to check.
     *
     * @return bool True if the class is allowed to be registered, false otherwise.
     */
    
protected function is_class_allowedstring $class_name ): bool {
        return 
StringUtil::starts_with$class_nameself::WOOCOMMERCE_NAMESPACEfalse );
    }

    
/**
     * Tells if this class should be used as the core WooCommerce dependency injection container (or if the old ExtendedContainer should be used instead).
     *
     * By default, this returns true, to have it return false you can:
     *
     * 1. Define the WOOCOMMERCE_USE_OLD_DI_CONTAINER constant with a value of true; or
     * 2. Hook on the 'woocommerce_use_old_di_container' filter and have it return false (it receives the value of WOOCOMMERCE_USE_OLD_DI_CONTAINER, or false if the constant doesn't exist).
     *
     * @return bool True if this class should be used as the core WooCommerce dependency injection container, false if ExtendedContainer should be used instead.
     */
    
public static function should_use(): bool {
        
$should_use = ! defined'WOOCOMMERCE_USE_OLD_DI_CONTAINER' ) || true !== WOOCOMMERCE_USE_OLD_DI_CONTAINER;

        
/**
         * Hook to decide if the old ExtendedContainer class (instead of RuntimeContainer) should be used as the underlying WooCommerce dependency injection container.
         *
         * NOTE: This hook will be removed in WooCommerce 9.5.
         *
         * @param bool $should_use Value of the WOOCOMMERCE_USE_OLD_DI_CONTAINER constant, false if the constant doesn't exist.
         *
         * @since 9.5.0
         */
        
return apply_filters'woocommerce_use_old_di_container'$should_use );
    }
}