PHP_Engine   A
last analyzed

Complexity

Total Complexity 22

Size/Duplication

Total Lines 203
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 58
c 5
b 0
f 0
dl 0
loc 203
rs 10
wmc 22

11 Methods

Rating   Name   Duplication   Size   Complexity  
A set_component_compiler() 0 2 1
A __construct() 0 2 1
A render() 0 7 2
A maybe_resolve_dot_notation() 0 9 2
A partial() 0 6 2
A view_model() 0 2 1
A render_buffer() 0 26 5
A resolve_file_path() 0 6 1
A component() 0 20 4
A base_view_path() 0 2 1
A verify_view_path() 0 9 2
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Basic PHP engine for using the Renderable interface.
7
 *
8
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
9
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
10
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
11
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
12
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
13
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
14
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
15
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
16
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
17
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
18
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19
 *
20
 * @author Glynn Quelch <[email protected]>
21
 * @license http://www.opensource.org/licenses/mit-license.html  MIT License
22
 * @package PinkCrab\Perique\View
23
 */
24
25
namespace PinkCrab\Perique\Services\View;
26
27
use Exception;
28
use PinkCrab\Perique\Utils\Object_Helper;
29
use PinkCrab\Perique\Interfaces\Renderable;
30
use PinkCrab\Perique\Services\View\View_Model;
31
use PinkCrab\Perique\Services\View\Component\Component;
32
use PinkCrab\Perique\Services\View\Component\Component_Compiler;
33
use function PinkCrab\FunctionConstructors\Strings\endsWith;
34
35
final class PHP_Engine implements Renderable {
36
37
	/**
38
	 * The path to base of templates.
39
	 *
40
	 * @var string
41
	 */
42
	private string $base_view_path;
43
44
	/**
45
	 * Access to the component compiler.
46
	 *
47
	 * @var Component_Compiler
48
	 */
49
	private ?Component_Compiler $component_compiler = null;
50
51
	/**
52
	 * Creates an instance of the PHP_Engine
53
	 *
54
	 * @param string $base_view_path
55
	 */
56
	public function __construct( string $base_view_path ) {
57
		$this->base_view_path = $this->verify_view_path( $base_view_path );
58
	}
59
60
	/**
61
	 * Sets the component compiler.
62
	 *
63
	 * @param Component_Compiler $compiler
64
	 * @return void
65
	 */
66
	public function set_component_compiler( Component_Compiler $compiler ): void {
67
		$this->component_compiler = $compiler;
68
	}
69
70
	/**
71
	 * Renders a template with data.
72
	 *
73
	 * @param string                  $view
74
	 * @param iterable<string, mixed> $data
75
	 * @param boolean                 $print
76
	 * @return string|null
77
	 */
78
	public function render( string $view, iterable $data, bool $print = true ): ?string {
79
		$view = $this->resolve_file_path( $view );
80
		if ( $print ) {
81
			print( $this->render_buffer( $view, $data ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
82
			return null;
83
		} else {
84
			return $this->render_buffer( $view, $data );
85
		}
86
	}
87
88
	/**
89
	 * Renders a component.
90
	 *
91
	 * @param Component $component
92
	 * @return string
93
	 */
94
	public function component( Component $component, bool $print = true ): ?string {
95
96
		// Throw exception of no compiler passed.
97
		if ( ! Object_Helper::is_a( $this->component_compiler, Component_Compiler::class ) ) {
98
			throw new Exception( 'No component compiler passed to PHP_Engine' );
99
		}
100
101
		// Compile the component.
102
		$compiled = $this->component_compiler->compile( $component ); // @phpstan-ignore-line, checked above.
0 ignored issues
show
Bug introduced by
The method compile() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

102
		/** @scrutinizer ignore-call */ 
103
  $compiled = $this->component_compiler->compile( $component ); // @phpstan-ignore-line, checked above.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
103
		$template = $compiled->template();
104
105
		$view = file_exists( $template )
106
			? $template
107
			: sprintf( '%s%s%s.php', $this->base_view_path, \DIRECTORY_SEPARATOR, trim( $this->maybe_resolve_dot_notation( $template ) ) );
108
109
		if ( $print ) {
110
			print( $this->render_buffer( $view, $compiled->data() ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
111
			return null;
112
		} else {
113
			return $this->render_buffer( $view, $compiled->data() );
114
		}
115
	}
116
117
	/**
118
	 * Renders a view Model
119
	 *
120
	 * @param View_Model $view_model
121
	 * @return string|null
122
	 */
123
	public function view_model( View_Model $view_model, bool $print = true ): ?string {
124
		return $this->render( $view_model->template(), $view_model->data(), $print );
125
	}
126
127
	/**
128
	 * Include a partial sub template
129
	 *
130
	 * @param string                  $view
131
	 * @param iterable<string, mixed> $data
132
	 * @param boolean                 $print
133
	 * @return string|null
134
	 */
135
	public function partial( string $view, iterable $data = array(), bool $print = true ): ?string {
136
		if ( $print ) {
137
			$this->render( $view, $data, $print );
138
			return null;
139
		} else {
140
			return $this->render( $view, $data, $print );
141
		}
142
	}
143
144
	/**
145
	 * Builds the view.
146
	 *
147
	 * @param string                  $view
148
	 * @param iterable<string, mixed> $__data
149
	 * @return string
150
	 * @throws Exception
151
	 */
152
	private function render_buffer( string $view, iterable $__data ): string {
153
154
		if ( ! file_exists( $view ) ) {
155
			throw new Exception( esc_html( "{$view} doesn't exist" ) );
156
		}
157
158
		$output = '';
159
		ob_start();
160
161
		// Set all the data values a parameters.
162
		foreach ( $__data as $__key => $__value ) {
163
			if ( is_string( $__key ) ) {
164
				${\wp_strip_all_tags( $__key )} = $__value;
165
			}
166
167
			// Unset the key and value.
168
			unset( $__key, $__value );
169
		}
170
171
		// Unset the data.
172
		unset( $__data );
173
174
		include $view;
175
		$output = ob_get_contents();
176
		ob_end_clean();
177
		return $output ?: ''; // phpcs:ignore Universal.Operators.DisallowShortTernary.Found
178
	}
179
180
	/**
181
	 * Resolves the filepath from a filename.
182
	 *
183
	 * @param string $filename
184
	 * @return string
185
	 */
186
	private function resolve_file_path( string $filename ): string {
187
		$filename = $this->maybe_resolve_dot_notation( $filename );
188
		return sprintf(
189
			'%s%s.php',
190
			$this->base_view_path,
191
			trim( $filename )
192
		);
193
	}
194
195
	/**
196
	 * Replaces dots with directory separator based on OS from $filename
197
	 *
198
	 * @param string $filename
199
	 * @return string
200
	 */
201
	private function maybe_resolve_dot_notation( string $filename ): string {
202
		if ( endsWith( '.php' )( $filename ) ) {
203
			$filename = substr( $filename, 0, -4 );
204
		}
205
206
		$parts    = explode( '.', $filename );
207
		$filename = implode( DIRECTORY_SEPARATOR, $parts );
208
209
		return $filename;
210
	}
211
212
	/**
213
	 * Verifies the view path exists and it has the trailing slash.
214
	 *
215
	 * @param string $path
216
	 * @return string
217
	 * @throws Exception
218
	 */
219
	private function verify_view_path( string $path ): string {
220
		$path = $this->maybe_resolve_dot_notation( $path );
221
		$path = rtrim( $path, '/' ) . '/';
222
223
		if ( ! \is_dir( $path ) ) {
224
			throw new Exception( esc_html( "{$path} doesn't exist and cant be used as the base view path." ) );
225
		}
226
227
		return $path;
228
	}
229
230
	/**
231
	 * Returns the base view path.
232
	 *
233
	 * @return string
234
	 * @since 1.4.0
235
	 */
236
	public function base_view_path(): string {
237
		return $this->base_view_path;
238
	}
239
}
240