Passed
Push — master ( 91184e...06a1cf )
by Aimeos
07:58
created

Frontend   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 206
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 21
eloc 57
dl 0
loc 206
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A cache() 0 4 1
A inject() 0 3 1
A addControllerDecorators() 0 50 3
A createController() 0 20 4
A addDecorators() 0 28 6
A create() 0 26 6
1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2015-2022
6
 * @package Controller
7
 * @subpackage Frontend
8
 */
9
10
11
namespace Aimeos\Controller;
12
13
14
/**
15
 * Factory which can create all Frontend controllers
16
 *
17
 * @package Controller
18
 * @subpackage Frontend
19
 */
20
class Frontend
21
{
22
	private static $cache = true;
23
	private static $objects = [];
24
25
26
	/**
27
	 * Enables or disables caching of class instances
28
	 *
29
	 * @param bool $value True to enable caching, false to disable it.
30
	 */
31
	public static function cache( bool $value )
32
	{
33
		self::$cache = (boolean) $value;
34
		self::$objects = [];
35
	}
36
37
38
	/**
39
	 * Creates the required controller specified by the given path of controller names
40
	 *
41
	 * Controllers are created by providing only the domain name, e.g.
42
	 * "basket" for the \Aimeos\Controller\Frontend\Basket\Standard or a path of names to
43
	 * retrieve a specific sub-controller if available.
44
	 * Please note, that only the default controllers can be created. If you need
45
	 * a specific implementation, you need to use the factory class of the
46
	 * controller to hand over specifc implementation names.
47
	 *
48
	 * @param \Aimeos\MShop\ContextIface $context Context object required by managers
49
	 * @param string $path Name of the domain (and sub-managers) separated by slashes, e.g "basket"
50
	 * @return \Aimeos\Controller\Frontend\Iface New frontend controller
51
	 * @throws \Aimeos\Controller\Frontend\Exception If the given path is invalid or the manager wasn't found
52
	 */
53
	public static function create( \Aimeos\MShop\ContextIface $context, string $path )
54
	{
55
		if( empty( $path ) ) {
56
			throw new \Aimeos\Controller\Frontend\Exception( 'Controller path is empty', 400 );
57
		}
58
59
		if( empty( $name ) ) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $name seems to never exist and therefore empty should always be true.
Loading history...
60
			$name = $context->config()->get( 'controller/frontend/' . $path . '/name', 'Standard' );
61
		}
62
63
		$iface = '\\Aimeos\\Controller\\Frontend\\' . ucfirst( $path ) . '\\Iface';
64
		$classname = '\\Aimeos\\Controller\\Frontend\\' . ucfirst( $path ) . '\\' . $name;
65
66
		if( self::$cache === false || !isset( self::$objects[$classname] ) )
67
		{
68
			if( class_exists( $classname ) === false ) {
69
				throw new \Aimeos\Controller\Frontend\Exception( sprintf( 'Class "%1$s" not found', $classname, 404 ) );
70
			}
71
72
			$cntl = self::createController( $context, $classname, $iface );
73
			$cntl = self::addControllerDecorators( $context, $cntl, $path );
74
75
			self::$objects[$classname] = $cntl;
76
		}
77
78
		return clone self::$objects[$classname];
79
	}
80
81
82
	/**
83
	 * Injects a manager object for the given path of manager names
84
	 *
85
	 * This method is for testing only and you must call \Aimeos\MShop::cache( false )
86
	 * afterwards!
87
	 *
88
	 * @param string $classname Full name of the class for which the object should be returned
89
	 * @param \Aimeos\Controller\Frontend\Iface|null $object Frontend controller object for the given name or null to clear
90
	 */
91
	public static function inject( string $classname, \Aimeos\Controller\Frontend\Iface $object = null )
92
	{
93
		self::$objects['\\' . ltrim( $classname, '\\' )] = $object;
94
	}
95
96
97
	/**
98
	 * Adds the decorators to the controller object.
99
	 *
100
	 * @param \Aimeos\MShop\ContextIface $context Context instance with necessary objects
101
	 * @param \Aimeos\Controller\Frontend\Common\Iface $controller Controller object
102
	 * @param string $domain Domain name in lower case, e.g. "product"
103
	 * @return \Aimeos\Controller\Frontend\Iface Controller object
104
	 */
105
	protected static function addControllerDecorators( \Aimeos\MShop\ContextIface $context,
106
		\Aimeos\Controller\Frontend\Iface $controller, string $domain ) : \Aimeos\Controller\Frontend\Iface
107
	{
108
		$localClass = str_replace( '/', '\\', ucwords( $domain, '/' ) );
109
		$config = $context->config();
110
111
		/** controller/frontend/common/decorators/default
112
		 * Configures the list of decorators applied to all frontend controllers
113
		 *
114
		 * Decorators extend the functionality of a class by adding new aspects
115
		 * (e.g. log what is currently done), executing the methods of the underlying
116
		 * class only in certain conditions (e.g. only for logged in users) or
117
		 * modify what is returned to the caller.
118
		 *
119
		 * This option allows you to configure a list of decorator names that should
120
		 * be wrapped around the original instance of all created controllers:
121
		 *
122
		 *  controller/frontend/common/decorators/default = array( 'decorator1', 'decorator2' )
123
		 *
124
		 * This would wrap the decorators named "decorator1" and "decorator2" around
125
		 * all controller instances in that order. The decorator classes would be
126
		 * "\Aimeos\Controller\Frontend\Common\Decorator\Decorator1" and
127
		 * "\Aimeos\Controller\Frontend\Common\Decorator\Decorator2".
128
		 *
129
		 * @param array List of decorator names
130
		 * @since 2014.03
131
		 * @category Developer
132
		 */
133
		$decorators = $config->get( 'controller/frontend/common/decorators/default', [] );
134
		$excludes = $config->get( 'controller/frontend/' . $domain . '/decorators/excludes', [] );
135
136
		foreach( $decorators as $key => $name )
137
		{
138
			if( in_array( $name, $excludes ) ) {
139
				unset( $decorators[$key] );
140
			}
141
		}
142
143
		$classprefix = '\\Aimeos\\Controller\\Frontend\\Common\\Decorator\\';
144
		$controller = self::addDecorators( $context, $controller, $decorators, $classprefix );
145
146
		$classprefix = '\\Aimeos\\Controller\\Frontend\\Common\\Decorator\\';
147
		$decorators = $config->get( 'controller/frontend/' . $domain . '/decorators/global', [] );
148
		$controller = self::addDecorators( $context, $controller, $decorators, $classprefix );
149
150
		$classprefix = '\\Aimeos\\Controller\\Frontend\\' . ucfirst( $localClass ) . '\\Decorator\\';
151
		$decorators = $config->get( 'controller/frontend/' . $domain . '/decorators/local', [] );
152
		$controller = self::addDecorators( $context, $controller, $decorators, $classprefix );
153
154
		return $controller->setObject( $controller );
155
	}
156
157
158
	/**
159
	 * Adds the decorators to the controller object.
160
	 *
161
	 * @param \Aimeos\MShop\ContextIface $context Context instance with necessary objects
162
	 * @param \Aimeos\Controller\Frontend\Common\Iface $controller Controller object
163
	 * @param array $decorators List of decorator names that should be wrapped around the controller object
164
	 * @param string $classprefix Decorator class prefix, e.g. "\Aimeos\Controller\Frontend\Basket\Decorator\"
165
	 * @return \Aimeos\Controller\Frontend\Iface Controller object
166
	 */
167
	protected static function addDecorators( \Aimeos\MShop\ContextIface $context, \Aimeos\Controller\Frontend\Iface $controller,
168
		array $decorators, string $classprefix ) : \Aimeos\Controller\Frontend\Iface
169
	{
170
		foreach( $decorators as $name )
171
		{
172
			if( ctype_alnum( $name ) === false )
173
			{
174
				$classname = is_string( $name ) ? $classprefix . $name : '<not a string>';
175
				throw new \Aimeos\Controller\Frontend\Exception( sprintf( 'Invalid class name "%1$s"', $classname ), 400 );
176
			}
177
178
			$classname = $classprefix . $name;
179
180
			if( class_exists( $classname ) === false ) {
181
				throw new \Aimeos\Controller\Frontend\Exception( sprintf( 'Class "%1$s" not found', $classname ), 404 );
182
			}
183
184
			$interface = '\\Aimeos\\Controller\\Frontend\\Common\\Decorator\\Iface';
185
			$controller = new $classname( $controller, $context );
186
187
			if( !( $controller instanceof $interface ) )
188
			{
189
				$msg = sprintf( 'Class "%1$s" does not implement "%2$s"', $classname, $interface );
190
				throw new \Aimeos\Controller\Frontend\Exception( $msg, 400 );
191
			}
192
		}
193
194
		return $controller;
195
	}
196
197
198
	/**
199
	 * Creates a controller object.
200
	 *
201
	 * @param \Aimeos\MShop\ContextIface $context Context instance with necessary objects
202
	 * @param string $classname Name of the controller class
203
	 * @param string $interface Name of the controller interface
204
	 * @return \Aimeos\Controller\Frontend\Iface Controller object
205
	 */
206
	protected static function createController( \Aimeos\MShop\ContextIface $context,
207
		string $classname, string $interface ) : \Aimeos\Controller\Frontend\Iface
208
	{
209
		if( isset( self::$objects[$classname] ) ) {
210
			return self::$objects[$classname];
211
		}
212
213
		if( class_exists( $classname ) === false ) {
214
			throw new \Aimeos\Controller\Frontend\Exception( sprintf( 'Class "%1$s" not found', $classname ), 404 );
215
		}
216
217
		$controller = new $classname( $context );
218
219
		if( !( $controller instanceof $interface ) )
220
		{
221
			$msg = sprintf( 'Class "%1$s" does not implement "%2$s"', $classname, $interface );
222
			throw new \Aimeos\Controller\Frontend\Exception( $msg, 400 );
223
		}
224
225
		return $controller;
226
	}
227
}
228