Passed
Push — master ( 552949...84ddcd )
by Aimeos
09:13 queued 06:44
created

Html::createComponent()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 9
nc 4
nop 3
dl 0
loc 19
rs 9.9666
c 1
b 0
f 0
1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2015-2022
6
 * @package Client
7
 * @subpackage Html
8
 */
9
10
11
namespace Aimeos\Client;
12
13
14
/**
15
 * Common factory for HTML clients
16
 *
17
 * @package Client
18
 * @subpackage Html
19
 */
20
class Html
21
{
22
	private static $objects = [];
23
24
25
	/**
26
	 * Creates a new client object
27
	 *
28
	 * @param \Aimeos\MShop\ContextIface $context Shop context instance with necessary objects
29
	 * @param string $path Type of the client, e.g 'account/favorite' for \Aimeos\Client\Html\Account\Favorite\Standard
30
	 * @param string|null $name Client name (default: "Standard")
31
	 * @return \Aimeos\Client\Html\Iface HTML client implementing \Aimeos\Client\Html\Iface
32
	 * @throws \Aimeos\Client\Html\Exception If requested client implementation couldn't be found or initialisation fails
33
	 */
34
	public static function create( \Aimeos\MShop\ContextIface $context, string $path, string $name = null ) : \Aimeos\Client\Html\Iface
35
	{
36
		if( empty( $path ) ) {
37
			throw new \Aimeos\Client\Html\Exception( 'Component path is empty', 400 );
38
		}
39
40
		if( empty( $name ) ) {
41
			$name = $context->config()->get( 'client/html/' . $path . '/name', 'Standard' );
42
		}
43
44
		$interface = '\\Aimeos\\Client\Html\\Iface';
45
		$classname = '\\Aimeos\\Client\\Html\\' . str_replace( '/', '\\', ucwords( $path, '/' ) ) . '\\' . $name;
46
47
		if( class_exists( $classname ) === false ) {
48
			throw new \Aimeos\Client\Html\Exception( sprintf( 'Class "%1$s" not found', $classname, 404 ) );
49
		}
50
51
		$client = self::createComponent( $context, $classname, $interface );
52
		$client = self::addComponentDecorators( $context, $client, $path );
53
54
		return $client->setObject( $client );
55
	}
56
57
58
	/**
59
	 * Injects a client object.
60
	 * The object is returned via create() if an instance of the class
61
	 * with the name name is requested.
62
	 *
63
	 * @param string $classname Full name of the class for which the object should be returned
64
	 * @param \Aimeos\Client\Html\Iface|null $client ExtJS client object
65
	 */
66
	public static function inject( string $classname, \Aimeos\Client\Html\Iface $client = null )
67
	{
68
		self::$objects['\\' . ltrim( $classname, '\\' )] = $client;
69
	}
70
71
72
	/**
73
	 * Adds the decorators to the client object.
74
	 *
75
	 * @param \Aimeos\MShop\ContextIface $context Context instance with necessary objects
76
	 * @param \Aimeos\Client\Html\Iface $client Client object
77
	 * @param string $path Path of the client in lower case, e.g. "catalog/detail"
78
	 * @return \Aimeos\Client\Html\Iface Client object
79
	 */
80
	protected static function addComponentDecorators( \Aimeos\MShop\ContextIface $context,
81
		\Aimeos\Client\Html\Iface $client, string $path ) : \Aimeos\Client\Html\Iface
82
	{
83
		$localClass = str_replace( '/', '\\', ucwords( $path, '/' ) );
84
		$config = $context->config();
85
86
		/** client/html/common/decorators/default
87
		 * Configures the list of decorators applied to all html clients
88
		 *
89
		 * Decorators extend the functionality of a class by adding new aspects
90
		 * (e.g. log what is currently done), executing the methods of the underlying
91
		 * class only in certain conditions (e.g. only for logged in users) or
92
		 * modify what is returned to the caller.
93
		 *
94
		 * This option allows you to configure a list of decorator names that should
95
		 * be wrapped around the original instance of all created clients:
96
		 *
97
		 *  client/html/common/decorators/default = array( 'decorator1', 'decorator2' )
98
		 *
99
		 * This would wrap the decorators named "decorator1" and "decorator2" around
100
		 * all client instances in that order. The decorator classes would be
101
		 * "\Aimeos\Client\Html\Common\Decorator\Decorator1" and
102
		 * "\Aimeos\Client\Html\Common\Decorator\Decorator2".
103
		 *
104
		 * @param array List of decorator names
105
		 * @since 2014.03
106
		 */
107
		$decorators = $config->get( 'client/html/common/decorators/default', [] );
108
		$excludes = $config->get( 'client/html/' . $path . '/decorators/excludes', [] );
109
110
		foreach( $decorators as $key => $name )
111
		{
112
			if( in_array( $name, $excludes ) ) {
113
				unset( $decorators[$key] );
114
			}
115
		}
116
117
		$classprefix = '\\Aimeos\\Client\\Html\\Common\\Decorator\\';
118
		$client = self::addDecorators( $context, $client, $decorators, $classprefix );
119
120
		$classprefix = '\\Aimeos\\Client\\Html\\Common\\Decorator\\';
121
		$decorators = $config->get( 'client/html/' . $path . '/decorators/global', [] );
122
		$client = self::addDecorators( $context, $client, $decorators, $classprefix );
123
124
		$classprefix = '\\Aimeos\\Client\\Html\\' . $localClass . '\\Decorator\\';
125
		$decorators = $config->get( 'client/html/' . $path . '/decorators/local', [] );
126
		$client = self::addDecorators( $context, $client, $decorators, $classprefix );
127
128
		return $client;
129
	}
130
131
132
	/**
133
	 * Adds the decorators to the client object.
134
	 *
135
	 * @param \Aimeos\MShop\ContextIface $context Context instance with necessary objects
136
	 * @param \Aimeos\Client\Html\Iface $client Client object
137
	 * @param array $decorators List of decorator name that should be wrapped around the client
138
	 * @param string $classprefix Decorator class prefix, e.g. "\Aimeos\Client\Html\Catalog\Decorator\"
139
	 * @return \Aimeos\Client\Html\Iface Client object
140
	 */
141
	protected static function addDecorators( \Aimeos\MShop\ContextIface $context,
142
		\Aimeos\Client\Html\Iface $client, array $decorators, string $classprefix ) : \Aimeos\Client\Html\Iface
143
	{
144
		foreach( $decorators as $name )
145
		{
146
			if( ctype_alnum( $name ) === false )
147
			{
148
				$classname = is_string( $name ) ? $classprefix . $name : '<not a string>';
149
				throw new \Aimeos\Client\Html\Exception( sprintf( 'Invalid class name "%1$s"', $classname ), 400 );
150
			}
151
152
			$classname = $classprefix . $name;
153
154
			if( class_exists( $classname ) === false ) {
155
				throw new \Aimeos\Client\Html\Exception( sprintf( 'Class "%1$s" not found', $classname ), 404 );
156
			}
157
158
			$interface = '\\Aimeos\\Client\\Html\\Common\\Decorator\\Iface';
159
			$client = new $classname( $client, $context );
160
161
			if( !( $client instanceof $interface ) )
162
			{
163
				$msg = sprintf( 'Class "%1$s" does not implement "%2$s"', $classname, $interface );
164
				throw new \Aimeos\Client\Html\Exception( $msg, 400 );
165
			}
166
		}
167
168
		return $client;
169
	}
170
171
172
	/**
173
	 * Creates a client object.
174
	 *
175
	 * @param \Aimeos\MShop\ContextIface $context Context instance with necessary objects
176
	 * @param string $classname Name of the client class
177
	 * @param string $interface Name of the client interface
178
	 * @return \Aimeos\Client\Html\Iface Client object
179
	 * @throws \Aimeos\Client\Html\Exception If client couldn't be found or doesn't implement the interface
180
	 */
181
	protected static function createComponent( \Aimeos\MShop\ContextIface $context, string $classname, string $interface ) : \Aimeos\Client\Html\Iface
182
	{
183
		if( isset( self::$objects[$classname] ) ) {
184
			return self::$objects[$classname];
185
		}
186
187
		if( class_exists( $classname ) === false ) {
188
			throw new \Aimeos\Client\Html\Exception( sprintf( 'Class "%1$s" not available', $classname ), 404 );
189
		}
190
191
		$client = new $classname( $context );
192
193
		if( !( $client instanceof $interface ) )
194
		{
195
			$msg = sprintf( 'Class "%1$s" does not implement "%2$s"', $classname, $interface );
196
			throw new \Aimeos\Client\Html\Exception( $msg, 400 );
197
		}
198
199
		return $client;
200
	}
201
}
202