Темплейт клас подобряване

dakata__92

Super Moderator
Колеги, написах си един клас за прости темплейтни системи, но ми дава много ограничения какъв код при сетване да ми замества. Основно само стринг грябва да е, което е и логично естествено но искам да го подобря. Много ме ограничава.
Викам го така:
PHP:
$template = TemplateEngine::getInstance();
$template->set("title",'Заглавие');
$template->set("body", $template->render('t2.tpl'));
$template->set("profile",'Профил текст');
print $template->render('t1.tpl');
Така работи, но някак си не е гъвкаво писането. Трябва всеки един масив ако ми се налага да въвеждам някъде предварително да го разпиша и немога функции да ползвам някак си ми е постен. Помогнете а ето го и самият клас.
PHP:
<?php
namespace System\Core;

class TemplateEngine 
{
	use \System\Traits\Singleton;
	
	private $file = false;
	private $data = array();
	
	public function set($key, $value) 
	{
		$this->data[$key] = $value;
	}
	
	public function render($file)
	{
		if ($this->isFileExists($file)) {
			$this->file = $file;
		}
		
		$fgc = file_get_contents($this->file);
  
		foreach ($this->data as $key => $value) {
			$tagToReplace = "{[$key]}";
			$fgc = str_replace($tagToReplace, $value, $fgc);
		}
		
		return $fgc;
	}
	
	private function isFileExists($file) 
	{
		if (file_exists($file) and is_readable($file)) {
			return true;
		}
		throw new \Exception("File not exists or it is not readable!");
	}

}
 
Така е малко тъпо, защо не заменяш {{$var}} примерно със <?=$var;?>
И да направиш при render да приема и масив с променливите, а не да ги сетваш една по една
 
lam3r4370 каза:
Така е малко тъпо, защо не заменяш {{$var}} примерно със <?=$var;?>
И да направиш при render да приема и масив с променливите, а не да ги сетваш една по една

За render добре, там те разбрах какво имаше предвид. Това не разбрах:

<?=$var;?>

как ще се получи и какво ще прави? Сподели ми пример за по-лесното използване на класът.

Взимам с file_get_contents() текстовият файл и заменям стринга {[$key]} тоест пример веднага давам:
HTML:
<!DOCTYPE html>
<html>
<head>
{[head]}
</head>
<body>
{[body]}
</body>
</html>
 
anonimen каза:
Да не би да търсиш нещо като ArrayAccess? Или magic __set/get?
Не то ако ставаше на въпрос винаги мога да тресна $template->set("body", print_r($array, true));
и str_replace ще го интерпретира като текст, просто някак си ми стои прекалено постнто и трудуемко за реализация на кода. Постоянно трябва да пренаписвам. Примерно имам в рутера въведени следните страници:

PHP:
$cnf['route_tree'] = array(
	array(
		"route" => "/",
		"action" => new \Model\Controller\Performance(),
		"method" => "ALL",
		"visibility" => 1
	),
	array(
		"route" => "/home",
		"action" => new \Model\Controller\Home(),
		"method" => "ALL",
		"visibility" => 1
	),
	array(
		"route" => "/edit",
		"action" => new \Model\Controller\Edit(),
		"method" => "ALL",
		"visibility" => 1
	)
);

return $cnf;

Като action - ите са класовете в които визуализирам страниците примерно нещо такова:

PHP:
<?php
namespace Model\Controller;

use \System\Core\FrontController;

class Home extends FrontController
{
	private $router = null;
	
	public function __construct()
	{
		$this->router = parent::getRouter();
	}
	
	public function view()
	{
		$tplFolderPath = dirname(dirname(__DIR__)).DIRECTORY_SEPARATOR.'Model'.DIRECTORY_SEPARATOR.'View'.DIRECTORY_SEPARATOR;
		$template = $this->getTemplateEngine();
		$template->set("mainhead",$template->render($tplFolderPath.'mainhead.tpl'));
		$links = '<ul>';
		foreach ($this->router->getRoutesTree() as $key => $route) {
			if (empty($key)) {
				$key = "/";
			}
			$links .= "<li><a href='http://localhost/Classes/Admin/$key' >".$key."</a></li>";
		}
		$links .= '</ul>';

		$template->set("navigation",$links);
		$template->set("header",$template->render($tplFolderPath.'header.tpl'));
		$template->set("footer",$template->render($tplFolderPath.'footer.tpl'));
		$template->set("mainbody",$template->render($tplFolderPath.'mainbody.tpl'));
		return $template->render($tplFolderPath.'main.tpl');
	}
}

След това във FrontController-а принтирам изгледа:

PHP:
public function executeRouteAction()
	{
		$router = $this->getRouter();
		$find = $router->findRoute($_SERVER["REQUEST_METHOD"],$_SERVER['REQUEST_URI'])->getRouteParams();
		if (is_object($find['action'])) {
			print $find['action']->view();
		} else {
			print $find['action'];
		}
	}

Проблема е че във Home кода става мнооого зле за описване на темплейтите кое къде да се замести. Там ще трябва всяка стойност да я разписвам на някакъв масив ако имам и да я приравнявам на някаква променлива $link .= пострингово да правя конкатенация и после културно в темплейта да подам: $template->set("navigation",$links); примерно. Да не говорим, че ако искам да поставям файл във файл да го замествам с по-голяма дълбочина става пълна омазия. Няма подреденост в контролера подаден за изпълнение на FrontController-a.
 
Малко офтопик Дака, но по-принцип се чудя за какво ти е толкова сложно рутирането? Ако разбирам правилно ти на всеки контролер му описваш пътища, имена и не знам какво друго. В крайна сметка получаваш някакъв огромен масив, и ако ти се наложи да сменяш нещо в бъдеще ще е чудо. От цялата работа се получава че и в контролерите продължаваш да мяташ пътища и папки :D и става пълна каша. Това е моето мнение надявам се не се обиждаш. Аз за това и не обичам Ларавел и други подобни фреймуорци.
 
teroristd каза:
Малко офтопик Дака, но по-принцип се чудя за какво ти е толкова сложно рутирането? Ако разбирам правилно ти на всеки контролер му описваш пътища, имена и не знам какво друго. В крайна сметка получаваш някакъв огромен масив, и ако ти се наложи да сменяш нещо в бъдеще ще е чудо. От цялата работа се получава че и в контролерите продължаваш да мяташ пътища и папки :D и става пълна каша. Това е моето мнение надявам се не се обиждаш. Аз за това и не обичам Ларавел и други подобни фреймуорци.

Всъщност забележката ти е уместна и преди време съм мислил над нея. Впрочем рутера ми е много прост само си задавам контролера на страницата чрез неймспейсове а лоадъра ми направо си регистрира класовете и изобщо нищо не инклудвам. Отностно кода във Home класът той е тестови и съм го поставил само за да мъдря нещата иначе имам система за конфигурация и при стартиране имам константи за пътища оттам нататък проблема е нулев. Просто в този клас само тествам общата картина и не ми харесва Темплейт енджина. Драснах го но по неговата логика ми се струва мноооого трудуемко писането, а и с този цикъл и реплейс ми се струва че доста ще бави кода при по-голям брой променливи. Include функцията е в пъти по-бърза от комбинацията на foreach и str_replace + file_get_contents. Разгледах много уроци и теми но предпочитам да попитам защото моите заключения не са на база стабилен опит а на база стабилно четене и тестване.
 
Какво мислите за този клас?
http://chadminick.com/articles/simple-php-template-engine.html#sthash.jnggect5.dpbs
Май е по-добър от моят.

:idea: :)
 
Ами и на мен ми беше такава идеята, само че вместо extract да си include-ваш примерно масива с данните най-отгоре, не разбрах какво ти не разбра.

И да се използва така
Код:
$this->render('file.html', ['var'=>1,'var2'=>2]);

и render функцията да ти замества
Код:
{{var}}
със
Код:
<?=$var;?>
и после

Код:
include($data);
include($template);
 
То идеята ти я схванах, само неразбрас как да го заместя. Малко съм отвеян и изобщо не загрях вместо extract какво да ползвам. Драснах това поправи ме ако може.
PHP:
namespace System\Core;

class View 
{
	use \System\Traits\Singleton;
	
	public function render($file, Array $data)
	{
		if ($this->isFileExists($file) and $data) {
			extract($data);
			ob_start();
			include($file);
			$outputBuffer = ob_get_contents();
			ob_end_clean();
			return $outputBuffer;
		}
		return false;
	}
	
	private function isFileExists($file) 
	{
		if (file_exists($file) and is_readable($file)) {
			return true;
		}
		throw new \Exception("File not exists or it is not readable!");
	}

}
 
Това е друго нещо, както се получи. Има последователна структура!
В контролерите на страниците изграждам така:
PHP:
$template = $this->getTemplateEngine();
		return $template->render(MODEL_VIEW_FOLDER.'main.php',array(
			'head' => $template->render(MODEL_VIEW_FOLDER.'mainhead.php'), 
			'body' => $template->render(MODEL_VIEW_FOLDER.'mainbody.php',array(
				'header' => 'Еdit page<br>'.$template->render(MODEL_VIEW_FOLDER.'header.php',array(
					'navigation' => $links
				)),
				'footer' => $template->render(MODEL_VIEW_FOLDER.'footer.php')
			))
		));

В темплейта правя така:

PHP:
<!DOCTYPE html>
<html>
<head>
<?php print $head; ?>
</head>
<body>
<?php print $body; ?>
</body>
</html>

Рендера ми е:

PHP:
public function render($__file, Array $__data = Array())
	{
		if ($this->isFileExists($__file)) {
			if ($__data) {
				extract($__data);
			}
			ob_start();
			include($__file);
			$__outputBuffer = ob_get_contents();
			ob_end_clean();
			return $__outputBuffer;
		}
		return false;
	}

И мисля, че така е във пъъъти по-добре!
 
Да, както си го направил - с extract- e по-добре даже, аз не бях помислил. Така е наистина по-добре :)

Ако искаш разгледай как го правят в laravel с blade template engine-а
https://github.com/illuminate/view/

А сега помисли как вместо да правиш на отделните части ръчно render да го правиш автоматично, например:

file.html


Код:
@show('head.html')
<body>Body</body>
@show('footer.html')

Hint: намираш пак нещата между @show и ги заместваш със render автоматично, както при променливите
 
lam3r4370 каза:
Да, както си го направил - с extract- e по-добре даже, аз не бях помислил. Така е наистина по-добре :)

Ако искаш разгледай как го правят в laravel с blade template engine-а
https://github.com/illuminate/view/

А сега помисли как вместо да правиш на отделните части ръчно render да го правиш автоматично, например:

file.html


Код:
@show('head.html')
<body>Body</body>
@show('footer.html')

Hint: намираш пак нещата между @show и ги заместваш със render автоматично, както при променливите

Не се сещам как да го направя рендера да засича автоматично отделните части.

Offtopic:// А правилно ли е да принтирам във FrontController-а ? Реално него наследяват следващите котролери а той наследява ядрото, но връщайки отговорите от моделите / контролери то остава най-логично там да принтирам поне за мен.
 
Малко по-късно ще ти покажа, ако не са ме изпреварили.
Да, ок е от който и да е контролер да връщаш view
 
lam3r4370 каза:
Малко по-късно ще ти покажа, ако не са ме изпреварили.
Да, ок е от който и да е контролер да връщаш view
Доообре ще почакам да споделиш малко инфо :?:
 
Не съм го тествал, но би трябвало да работи.
С темплейт подобен на:
HTML:
@show('head.html')
<body>
Body {{$var}}
@show('footer.html')
</body>
PHP:
public function render($file, array $data) {
	if ($this->isFileExists($file)) {
		$this->file = $file;
	}

	$fgc = file_get_contents($this->file);
	$tagToReplace = "{[$key]}";
	$fgc = preg_replace('/{{(.*)}}/g', "<?=$2;?>", $fgc);
	$callback = function($matches) use($data) {
		$inc = $matches[0];
		return $this->render($inc, $data);
	}
	$fgc = preg_replace_callback('/@show\(\'(.*)\'\)/g', $callback, $fgc)
	ob_start();
	extract($data);
        echo $fgc;
        $ob = ob_get_contents();
        ob_end_clean();
	return $ob;
}
 
lam3r4370 каза:
Не съм го тествал, но би трябвало да работи.
С темплейт подобен на:
HTML:
@show('head.html')
<body>
Body {{$var}}
@show('footer.html')
</body>
PHP:
public function render($file, array $data) {
	if ($this->isFileExists($file)) {
		$this->file = $file;
	}

	$fgc = file_get_contents($this->file);
	$tagToReplace = "{[$key]}";
	$fgc = preg_replace('/{{(.*)}}/g', "<?=$2;?>", $fgc);
	$callback = function($matches) use($data) {
		$inc = $matches[0];
		return $this->render($inc, $data);
	}
	$fgc = preg_replace_callback('/@show\(\'(.*)\'\)/g', $callback, $fgc)
	ob_start();
	extract($data);
        echo $fgc;
        $ob = ob_get_contents();
        ob_end_clean();
	return $ob;
}

Схванах ти идеята, колега, но ти искаш от include да се върна на file_get_contents със str_replace или preg_replace. Правейки бенчмарк тест с инклуд е по-бърза системата. Идеята ти е много хубава, но как може и с последният ми код да стане. Реално погледнато от по-простата версия на твоят код избягах. :D
 
Ти така не правиш абсолютно нищо. Нали искаш темплейт система, каква темплейт система правиш като само вкарваш променливите и нищо друго?

Разликата в бързината са 2 preg_replace-а преди да се даде файла. При твоята версия ти заменяш всяка променлива със съдържанието й, докато аз заменям всичко между {{$var}} със
PHP:
<?=$var;?>
, т.е не ме интересува дали тази променлива съществува или не.

Edit:

PHP:
public function render($file, array $data) {
        if (!$this->isFileExists($file)) {
               throw new Exception('file does not exist');
        }
        ob_start();
        extract($data);
        include $file;
        $ob = ob_get_contents();
        ob_end_clean();
        $ob = preg_replace('/{{(.*)}}/g', "<?=$2;?>", $ob);
        $callback = function($matches) use($data) {
                return $this->render($matches[0], $data);
        }
        $ob = preg_replace_callback('/@show\(\'(.*)\'\)/g', $callback, $ob);
        return $ob;
}
 
lam3r4370 каза:
Ти така не правиш абсолютно нищо. Нали искаш темплейт система, каква темплейт система правиш като само вкарваш променливите и нищо друго?

Разликата в бързината са 2 preg_replace-а преди да се даде файла. При твоята версия ти заменяш всяка променлива със съдържанието й, докато аз заменям всичко между {{$var}} със
PHP:
<?=$var;?>
, т.е не ме интересува дали тази променлива съществува или не.

Edit:

PHP:
public function render($file, array $data) {
        if (!$this->isFileExists($file)) {
               throw new Exception('file does not exist');
        }
        ob_start();
        extract($data);
        include $file;
        $ob = ob_get_contents();
        ob_end_clean();
        $ob = preg_replace('/{{(.*)}}/g', "<?=$2;?>", $ob);
        $callback = function($matches) use($data) {
                return $this->render($matches[0], $data);
        }
        $ob = preg_replace_callback('/@show\(\'(.*)\'\)/g', $callback, $ob);
        return $ob;
}
Добре, де на този код как трябва да изглежда темплейта и най-вече как трябва да го викам в контролера?

В момента ми отпечатва нещата така в браузъра:
HTML:
<!DOCTYPE html>
<html>
<head>
<?=$head;?>
</head>
<body>
<?=$body;?>
</body>
</html>

Темплейта е :
HTML:
<!DOCTYPE html>
<html>
<head>
{{$head}}
</head>
<body>
{{$body}}
</body>
</html>

С мояд код в контролера си описвам точно какво да ми зарежда без да ми се налага да пиша в темплейта какво да зарежда и да го филтрирам там. Може на определена страница да не ми се налага да го правя. Още не намирам смисъла на това което се опитваш да ме накараш да правя.

Това ми е в един от контролерите за страниците. В друга страница може да ми се нлага да използвам същият темплейт но в тага да заредя къстъм нещо а не да подавам автоматично @show('head')
PHP:
return $template->render(MODEL_VIEW_FOLDER.'index.php',array(
			'head' => $template->render(MODEL_VIEW_FOLDER.'head.php'), 
			'body' => $template->render(MODEL_VIEW_FOLDER.'body.php',array(
				'header' => $template->render(MODEL_VIEW_FOLDER.'header.php',array(
					'navigation' => (new Navigation)->getResponse().PHP_EOL
				)),
				'main' => (new DashboardArticle)->getResponse().PHP_EOL,
				'footer' => $template->render(MODEL_VIEW_FOLDER.'footer.php')
			))
		));
 
Доколкото виждам ти му взимаш съдържанието в стринг, докато lam3r4370 ти предлага да го includeнеш.
 

Back
Горе