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

aimeos / aimeos-core / 4f8b7e8d-5595-43b2-ba5c-df9921830178

17 May 2026 07:32AM UTC coverage: 92.581%. Remained the same
4f8b7e8d-5595-43b2-ba5c-df9921830178

push

circleci

aimeos
Fixed PHPStan issues

896 of 980 new or added lines in 165 files covered. (91.43%)

35 existing lines in 29 files now uncovered.

9734 of 10514 relevant lines covered (92.58%)

80.53 hits per line

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

94.2
/src/MShop.php
1
<?php
2

3
/**
4
 * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2015-2026
6
 * @package MShop
7
 */
8

9

10
namespace Aimeos;
11

12

13
/**
14
 * Factory which can create all MShop managers
15
 *
16
 * @package MShop
17
 */
18
class MShop
19
{
20
        private static ?\Aimeos\MShop\ContextIface $context = null;
21
        private static bool $cache = true;
22
        private static array $objects = [];
23

24

25
        /**
26
         * Enables or disables caching of class instances and clears cache
27
         *
28
         * @param bool $value True to enable caching, false to disable it.
29
         * @return void
30
         */
31
        public static function cache( bool $value ) : void
32
        {
33
                self::$cache = (bool) $value;
108✔
34
                self::$context = null;
108✔
35
                self::$objects = [];
108✔
36
        }
37

38

39
        /**
40
         * Creates the required manager specified by the given path of manager names
41
         *
42
         * Domain managers are created by providing only the domain name, e.g.
43
         * "product" for the \Aimeos\MShop\Product\Manager\Standard or a path of names to
44
         * retrieve a specific sub-manager, e.g. "product/type" for the
45
         * \Aimeos\MShop\Product\Manager\Type\Standard manager.
46
         * Please note, that only the default managers can be created. If you need
47
         * a specific implementation, you need to use the factory class of the
48
         * domain or the getSubManager() method to hand over specifc implementation
49
         * names.
50
         *
51
         * @param \Aimeos\MShop\ContextIface $context Context object required by managers
52
         * @param string $path Name of the domain (and sub-managers) separated by slashes, e.g "product/list"
53
         * @param string|null $name Name of the controller implementation ("Standard" if null)
54
         * @return \Aimeos\MShop\Common\Manager\Iface Manager object
55
         * @throws \Aimeos\MShop\Exception If the given path is invalid or the manager wasn't found
56
         */
57
        public static function create( \Aimeos\MShop\ContextIface $context,
58
                string $path, ?string $name = null ) : \Aimeos\MShop\Common\Manager\Iface
59
        {
60
                $path = self::checkPath( $path );
1,083✔
61

62
                if( self::$context !== null && self::$context !== $context ) {
1,081✔
63
                        self::$objects = []; // clear cached objects on context change
967✔
64
                }
65

66
                self::$context = $context;
1,081✔
67
                $parts = explode( '/', $path );
1,081✔
68

69
                if( ( $domain = array_shift( $parts ) ) === null ) {
1,081✔
NEW
70
                        throw new \LogicException( sprintf( 'Manager path "%1$s" is empty', $path ), 400 );
×
71
                }
72

73
                $classname = self::classname( $context, $parts, $domain, $name );
1,081✔
74

75
                if( self::$cache === false || !isset( self::$objects[$classname] ) ) {
1,081✔
76
                        self::instantiate( $context, $parts, $domain, $name );
1,079✔
77
                }
78

79
                // @phpstan-ignore return.type
80
                return self::$objects[$classname]->setObject( self::$objects[$classname] );
1,079✔
81
        }
82

83

84
        /**
85
         * Injects a manager object for the given path of manager names
86
         *
87
         * This method is for testing only and you must call \Aimeos\MShop::cache( false )
88
         * afterwards!
89
         *
90
         * @param string $classname Full name of the class for which the object should be returned
91
         * @param \Aimeos\MShop\Common\Manager\Iface|null $object Manager object for the given manager path or null to clear
92
         * @return void
93
         */
94
        public static function inject( string $classname, ?\Aimeos\MShop\Common\Manager\Iface $object = null ) : void
95
        {
96
                self::$objects['\\' . ltrim( $classname, '\\' )] = $object;
35✔
97
        }
98

99

100
        /**
101
         * Adds the decorators to the manager object.
102
         *
103
         * @param \Aimeos\MShop\ContextIface $context Context instance with necessary objects
104
         * @param \Aimeos\MShop\Common\Manager\Iface $manager Manager object
105
         * @param array $decorators List of decorator names that should be wrapped around the manager object
106
         * @param string $classprefix Decorator class prefix, e.g. "\Aimeos\MShop\Product\Manager\Decorator\"
107
         * @return \Aimeos\MShop\Common\Manager\Iface Manager object
108
         * @throws \LogicException If class isn't found
109
         */
110
        protected static function addDecorators( \Aimeos\MShop\ContextIface $context,
111
                \Aimeos\MShop\Common\Manager\Iface $manager, array $decorators, string $classprefix ) : \Aimeos\MShop\Common\Manager\Iface
112
        {
113
                foreach( $decorators as $name )
1,078✔
114
                {
115
                        if( ctype_alnum( $name ) === false ) {
1,078✔
NEW
116
                                throw new \LogicException( sprintf( 'Invalid class name "%1$s"', (string) $name ), 400 );
×
117
                        }
118

119
                        $classname = $classprefix . $name;
1,078✔
120
                        $interface = \Aimeos\MShop\Common\Manager\Decorator\Iface::class;
1,078✔
121

122
                        $manager = \Aimeos\Utils::create( $classname, [$manager, $context], $interface );
1,078✔
123
                }
124

125
                // @phpstan-ignore return.type
126
                return $manager;
1,078✔
127
        }
128

129

130
        /**
131
         * Adds the decorators to the manager object.
132
         *
133
         * @param \Aimeos\MShop\ContextIface $context Context instance with necessary objects
134
         * @param \Aimeos\MShop\Common\Manager\Iface $manager Manager object
135
         * @param string $domain Domain name in lower case, e.g. "product"
136
         * @return \Aimeos\MShop\Common\Manager\Iface Manager object
137
         */
138
        protected static function addManagerDecorators( \Aimeos\MShop\ContextIface $context,
139
                \Aimeos\MShop\Common\Manager\Iface $manager, string $domain ) : \Aimeos\MShop\Common\Manager\Iface
140
        {
141
                $config = $context->config();
1,078✔
142

143
                $classprefix = '\Aimeos\MShop\\' . ucfirst( $domain ) . '\Manager\Decorator\\';
1,078✔
144
                $decorators = array_reverse( (array) $config->get( 'mshop/' . $domain . '/manager/decorators/local', [] ) );
1,078✔
145
                $manager = self::addDecorators( $context, $manager, $decorators, $classprefix );
1,078✔
146

147
                $classprefix = '\Aimeos\MShop\Common\Manager\Decorator\\';
1,078✔
148
                $decorators = array_reverse( (array) $config->get( 'mshop/' . $domain . '/manager/decorators/global', [] ) );
1,078✔
149
                $manager = self::addDecorators( $context, $manager, $decorators, $classprefix );
1,078✔
150

151
                /** mshop/common/manager/decorators/default
152
                 * Configures the list of decorators applied to all admin managers
153
                 *
154
                 * Decorators extend the functionality of a class by adding new aspects
155
                 * (e.g. log what is currently done), executing the methods of the underlying
156
                 * class only in certain conditions (e.g. only for logged in users) or
157
                 * modify what is returned to the caller.
158
                 *
159
                 * This option allows you to configure a list of decorator names that should
160
                 * be wrapped around the original instances of all created managers:
161
                 *
162
                 *  mshop/common/manager/decorators/default = array( 'decorator1', 'decorator2' )
163
                 *
164
                 * This would wrap the decorators named "decorator1" and "decorator2" around
165
                 * all controller instances in that order. The decorator classes would be
166
                 * "\Aimeos\MShop\Common\Manager\Decorator\Decorator1" and
167
                 * "\Aimeos\MShop\Common\Manager\Decorator\Decorator2".
168
                 *
169
                 * @type array List of decorator names
170
                 * @since 2014.03
171
                 */
172
                $decorators = array_reverse( (array) $config->get( 'mshop/common/manager/decorators/default', [] ) );
1,078✔
173
                $excludes = (array) $config->get( 'mshop/' . $domain . '/manager/decorators/excludes', [] );
1,078✔
174

175
                foreach( $decorators as $key => $name )
1,078✔
176
                {
177
                        if( in_array( $name, $excludes ) ) {
1,078✔
178
                                unset( $decorators[$key] );
×
179
                        }
180
                }
181

182
                $classprefix = '\Aimeos\MShop\Common\Manager\Decorator\\';
1,078✔
183
                $manager = self::addDecorators( $context, $manager, $decorators, $classprefix );
1,078✔
184

185
                return $manager;
1,078✔
186
        }
187

188

189
        /**
190
         * Validates the given path
191
         *
192
         * @param string $path Name of the domain (and sub-managers) separated by slashes, e.g "product/list"
193
         * @return string Sanitized path
194
         */
195
        protected static function checkPath( string $path ) : string
196
        {
197
                $path = trim( $path, '/' );
1,083✔
198

199
                if( empty( $path ) ) {
1,083✔
200
                        throw new \LogicException( 'Manager path is empty', 400 );
×
201
                }
202

203
                if( preg_match( '/^[a-z0-9\/]+$/', $path ) !== 1 ) {
1,083✔
204
                        throw new \LogicException( sprintf( 'Invalid component path "%1$s"', $path ), 400 );
2✔
205
                }
206

207
                return $path;
1,081✔
208
        }
209

210

211
        /**
212
         * Creates a manager object.
213
         *
214
         * @param \Aimeos\MShop\ContextIface $context Context instance with necessary objects
215
         * @param string $classname Name of the manager class
216
         * @param string $interface Name of the manager interface
217
         * @param string $domain Domain name in lower case, e.g. "product"
218
         * @return \Aimeos\MShop\Common\Manager\Iface Manager object
219
         */
220
        protected static function createManager( \Aimeos\MShop\ContextIface $context,
221
                string $classname, ?string $interface, string $domain ) : \Aimeos\MShop\Common\Manager\Iface
222
        {
223
                if( isset( self::$objects[$classname] ) ) {
1,079✔
224
                        // @phpstan-ignore return.type
225
                        return self::$objects[$classname];
426✔
226
                }
227

228
                $manager = \Aimeos\Utils::create( $classname, [$context], $interface );
1,079✔
229

230
                // @phpstan-ignore argument.type
231
                return self::addManagerDecorators( $context, $manager, $domain );
1,078✔
232
        }
233

234

235
        /**
236
         * Returns the class name for the manager object
237
         *
238
         * @param \Aimeos\MShop\ContextIface $context Context instance with necessary objects
239
         * @param array $parts List of sub-path parts (without domain)
240
         * @param string $domain Domain name (first part of the path)
241
         * @param string|null $name Name of the manager implementation
242
         * @return string Manager class name
243
         */
244
        protected static function classname( \Aimeos\MShop\ContextIface $context, array $parts, string $domain, ?string $name = null ) : string
245
        {
246
                /** @var array<string> $parts */
247
                $subClass = !empty( $parts ) ? ucwords( join( '\\', $parts ), '\\' ) . '\\' : '';
1,081✔
248
                $classname = '\\Aimeos\\MShop\\' . ucfirst( $domain ) . '\\Manager\\' . $subClass;
1,081✔
249

250
                $subPath = !empty( $parts ) ? join( '/', $parts ) . '/' : '';
1,081✔
251
                $localName = $name ?: $context->config()->get( 'mshop/' . $domain . '/manager/' . $subPath . 'name', 'Standard' );
1,081✔
252

253
                if( class_exists( $classname . $localName ) ) {
1,081✔
254
                        return $classname . $localName;
1,080✔
255
                }
256

257
                return $classname . 'Standard';
2✔
258
        }
259

260

261
        /**
262
         * Instantiates the manager objects for all parts of the path
263
         *
264
         * @param \Aimeos\MShop\ContextIface $context Context instance with necessary objects
265
         * @param array $parts List of sub-path parts (without domain)
266
         * @param string $domain Domain name (first part of the path)
267
         * @param string|null $name Name of the manager implementation
268
         * @return void
269
         */
270
        protected static function instantiate( \Aimeos\MShop\ContextIface $context, array $parts, string $domain, ?string $name = null ) : void
271
        {
272
                $classname = self::classname( $context, [], $domain, $name );
1,079✔
273
                $iface = '\\Aimeos\\MShop\\' . ucfirst( $domain ) . '\\Manager\\Iface';
1,079✔
274
                $manager = self::createManager( $context, $classname, interface_exists( $iface ) ? $iface : null, $domain );
1,079✔
275

276
                self::$objects[$classname] = $manager;
1,078✔
277
                $paths = [$domain => $manager];
1,078✔
278

279
                $subParts = [];
1,078✔
280
                $tmppath = $domain;
1,078✔
281
                $last = end( $parts );
1,078✔
282

283
                foreach( $parts as $part )
1,078✔
284
                {
285
                        $subParts[] = $part;
294✔
286
                        $localName = $part === $last ? $name : null;
294✔
287
                        $classname = self::classname( $context, $subParts, $domain, $localName );
294✔
288

289
                        $paths[$tmppath . '/' . $part] = $paths[$tmppath]->getSubManager( (string) $part, $localName );
294✔
290
                        $tmppath .= '/' . $part;
293✔
291

292
                        self::$objects[$classname] = $paths[$tmppath];
293✔
293
                }
294
        }
295
}
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