Passed
Pull Request — master (#117)
by Glynn
02:41
created

PHP_Engine::base_view_path()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 2
rs 10
cc 1
nc 1
nop 0
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\Interfaces\Renderable;
29
use PinkCrab\Perique\Services\View\View_Model;
30
use PinkCrab\Perique\Services\View\Component\Component;
31
use function PinkCrab\FunctionConstructors\Strings\endsWith;
32
use PinkCrab\Perique\Services\View\Component\Component_Compiler;
33
34
final class PHP_Engine implements Renderable {
35
36
	/**
37
	 * The path to base of templates.
38
	 *
39
	 * @var string
40
	 */
41
	private $base_view_path;
42
43
	/**
44
	 * Access to the component compiler.
45
	 *
46
	 * @var Component_Compiler
47
	 */
48
	private $component_compiler;
49
50
	/**
51
	 * Creates an instance of the PHP_Engine
52
	 *
53
	 * @param string $base_view_path
54
	 */
55
	public function __construct( string $base_view_path ) {
56
		$this->base_view_path = $this->verify_view_path( $base_view_path );
57
	}
58
59
	/**
60
	 * Sets the component compiler.
61
	 *
62
	 * @param Component_Compiler $compiler
63
	 * @return void
64
	 */
65
	public function set_component_compiler( Component_Compiler $compiler ): void {
66
		$this->component_compiler = $compiler;
67
	}
68
69
	/**
70
	 * Renders a template with data.
71
	 *
72
	 * @param string $view
73
	 * @param iterable<string, mixed> $data
74
	 * @param bool $print
75
	 * @return string|void
76
	 */
77
	public function render( string $view, iterable $data, bool $print = true ) {
78
		$view = $this->resolve_file_path( $view );
79
		if ( $print ) {
80
			print( $this->render_buffer( $view, $data ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
81
		} else {
82
			return $this->render_buffer( $view, $data );
83
		}
84
	}
85
86
	/**
87
	 * Renders a component.
88
	 *
89
	 * @param Component $component
90
	 * @return string|void
91
	 */
92
	public function component( Component $component, bool $print = true ) {
93
94
		// Throw exception of no compiler passed.
95
		if ( ! is_a( $this->component_compiler, Component_Compiler::class ) ) {
96
			throw new Exception( 'No component compiler passed to PHP_Engine' );
97
		}
98
99
		// Compile the component.
100
		$compiled = $this->component_compiler->compile( $component );
101
		$template = $this->maybe_resolve_dot_notation( $compiled->template() );
102
		$view     = sprintf( '%s%s.php', \DIRECTORY_SEPARATOR, trim( $template ) );
103
		if ( $print ) {
104
			print( $this->render_buffer( $view, $compiled->data() ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
105
		} else {
106
			return $this->render_buffer( $view, $compiled->data() );
107
		}
108
	}
109
110
111
	/**
112
	 * Renders a view Model
113
	 *
114
	 * @param View_Model $view_model
115
	 * @return string|void
116
	 */
117
	public function view_model( View_Model $view_model, bool $print = true ) {
118
		return $this->render( $view_model->template(), $view_model->data(), $print );
119
	}
120
121
	/**
122
	 * Include a partial sub template
123
	 *
124
	 * @param string $view
125
	 * @param iterable<string, mixed> $data
126
	 * @param bool $print
127
	 * @return string|void
128
	 */
129
	public function partial( string $view, iterable $data = array(), bool $print = true ) {
130
		if ( $print ) {
131
			$this->render( $view, $data, $print );
132
		} else {
133
			return $this->render( $view, $data, $print );
134
		}
135
	}
136
137
	/**
138
	 * Builds the view.
139
	 *
140
	 * @param string $view
141
	 * @param iterable<string, mixed> $__data
142
	 * @return string
143
	 * @throws Exception
144
	 */
145
	private function render_buffer( string $view, iterable $__data ): string {
146
147
		if ( ! file_exists( $view ) ) {
148
			throw new Exception( "{$view} doesn't exist" );
149
		}
150
151
		$output = '';
152
		ob_start();
153
154
		// Set all the data values a parameters.
155
		foreach ( $__data as $__key => $__value ) {
156
			if ( is_string( $__key ) ) {
157
				${\wp_strip_all_tags( $__key )} = $__value;
158
			}
159
160
			// Unset the key and value.
161
			unset( $__key, $__value );
162
		}
163
164
		// Unset the data.
165
		unset( $__data );
166
167
		include $view;
168
		$output = ob_get_contents();
169
		ob_end_clean();
170
		return $output ?: '';
171
	}
172
173
	/**
174
	 * Resolves the filepath from a filename.
175
	 *
176
	 * @param string $filename
177
	 * @return string
178
	 */
179
	private function resolve_file_path( string $filename ): string {
180
		$filename = $this->maybe_resolve_dot_notation( $filename );
181
		return sprintf(
182
			'%s%s.php',
183
			$this->base_view_path,
184
			trim( $filename )
185
		);
186
	}
187
188
	/**
189
	 * Replaces dots with directory separator based on OS from $filename
190
	 *
191
	 * @param string $filename
192
	 * @return string
193
	 */
194
	private function maybe_resolve_dot_notation( string $filename ): string {
195
		if ( endsWith( '.php' )( $filename ) ) {
196
			$filename = substr( $filename, 0, -4 );
197
		}
198
199
		$parts    = explode( '.', $filename );
200
		$filename = implode( DIRECTORY_SEPARATOR, $parts );
201
202
		return $filename;
203
	}
204
205
	/**
206
	 * Verifies the view path exists and it has the trailing slash.
207
	 *
208
	 * @param string $path
209
	 * @return string
210
	 * @throws Exception
211
	 */
212
	private function verify_view_path( string $path ): string {
213
		$path = $this->maybe_resolve_dot_notation( $path );
214
		$path = rtrim( $path, '/' ) . '/';
215
216
		if ( ! \is_dir( $path ) ) {
217
			throw new Exception( "{$path} doesn't exist and cant be used as the base view path." );
218
		}
219
220
		return $path;
221
	}
222
223
	/**
224
	 * Returns the base view path.
225
	 *
226
	 * @return string
227
	 * @since 1.4.0
228
	 */
229
	public function base_view_path(): string {
230
		return $this->base_view_path;
231
	}
232
}
233