Parser.php
<?php
namespace Phad\Test\Compilation;
class Parser extends \Phad\Tester {
/**
* @param $code code to `eval()`.
* @param $args args to `extract()` prior to `eval($code)`
* @param $expect the expected output from `eval($code)`
*
* @note prefix $code with `?>` if the $code has its own php open tags
*/
public function run_test_exec(string $code, array $args, string $expect){
extract($args);
// $_node = ['index'=>0];
$_index = isset($args['TestItem']) ? $args['TestItem']->data_index : null;
// var_dump($code);
// exit;
try {
ob_start();
eval($code);
$out = ob_get_clean();
$this->compare_lines($expect,$out);
} catch (\ParseError $e){
ob_end_clean();
echo "Parse error in eval'd code! You may want to prefix the code with `?>` so it's in html mode, not php mode";
echo "\n\n";
throw $e;
} }
/**
*
* Test a view with a single item. I don't even know how multiple items works yet.
* @return the PHTML doc used in parsing
*/
public function run_parse_test(string $view, string $target_html, array $var_exports, array $target_item_data){
$parser = new \Phad\DomParser();
$doc = new \Taeluf\PHTML($view);
$items = $parser->parse_doc($doc);
$item = $items[0];
// print_r($items[0]['html_code']);
// exit;
$this->compare_lines($target_html, $item['html_code']);
unset($item['html_code']);
// print_r($item);
$var_exports = array_map(function($v){return var_export($v,true);},$var_exports);
$target_data = array_merge($target_item_data, $var_exports);
$actual_data = [];
foreach ($target_data as $key=>$value){
if (!isset($item[$key]))continue;
$actual_data[$key] = $item[$key];
}
$this->compare($target_data, $actual_data);
return $doc;
}
public function compare_data(array $actual, array $target_data, array $target_data_var_exports){
$var_exports = $target_data_var_exports;
$var_exports = array_map(function($v){return var_export($v,true);}, $var_exports);
$target_data = array_merge($target_data, $var_exports);
//
// print_r($item_data);
// print_r($target_data);
// exit;
$actual_data = [];
foreach ($target_data as $key=>$value){
$actual_data[$key] = $actual[$key];
}
$this->compare($target_data, $actual_data);
}
/**
* @test doc output contains filter call
*/
public function testPropertyFilter(){
$view = <<<HTML
<div item="Blog">
<h1 prop="title" filter="test:whocares"></h1>
</div>
HTML;
$expect = <<<HTML
<div>
<h1><?=\$phad->filter('test:whocares',\$Blog->title)?></h1>
</div>
HTML;
$parser = new \Phad\DomParser();
$doc = new \Taeluf\PHTML($view);
$template_args = $parser->parse_doc($doc);
// echo $doc;
$this->compare_lines($expect, $template_args[0]['html_code']);
}
/**
* @test doc output for a single node with access attribute prior to an item node
*/
public function testAccessAttributeBeforeItem(){
$view = <<<HTML
<div access="role:cat">
<?php echo "that is how a cat do"; ?>
</div>
<div item="Blog">
<h1 prop="title"></h1>
</div>
HTML;
$parser = new \Phad\DomParser();
$doc = new \Taeluf\PHTML($view);
$template_args = $parser->parse_doc($doc);
echo $doc;
$this->str_contains(
$doc,
'<?php if ($phad->can_read_node(array (',
'<?php echo "that is how a cat do"; ?>',
'<h1><?=$Blog->title?></h1>',
);
}
/**
* @test doc output for a single node with access attribute
*/
public function testAccessAttribute(){
$view = <<<HTML
<div access="role:cat">
<?php echo "that is how a cat do"; ?>
</div>
HTML;
$expect = <<<HTML
<?php
if (\$phad_block===\Phad\Blocks::ROUTE_META){
return array (
);
}
?><?php
if ((\$phad_block??null)===\Phad\Blocks::SITEMAP_META){
return array (
);
}
?> <?php if (\$phad->can_read_node(array (
'access' => 'role:cat',
'tagName' => 'div',
))): ?>
<div>
<?php echo "that is how a cat do"; ?>
</div>
<?php else: \$phad->read_node_failed(array (
'access' => 'role:cat',
'tagName' => 'div',
)); ?>
<?php endif; ?>
HTML;
$parser = new \Phad\DomParser();
$doc = new \Taeluf\PHTML($view);
$template_args = $parser->parse_doc($doc);
// print_r($template_args);
// echo $doc;
$this->compare_lines($expect, $doc.'');
}
/**
* @test template_args for form
* @test main code for form (string lines comparison)
* @test executing form code
*/
public function testForm(){
$view = <<<HTML
<form item="Blog">
<input type="text" name="title">
</form>
HTML;
$expect = <<<HTML
<form action="" method="POST">
<input type="text" name="title" value="<?=\$Blog->title?>">
</form>
HTML;
$as_is = [
'item_name' => 'Blog',
'item_type' => 'form',
'ItemForeach' => 'Blog => $BlogRow',
'ItemName' => '$Blog',
'Item_Row' => '$BlogRow',
'ItemInfo' => '$BlogInfo',
'form_submit'=>true,
'form_properties'=>true,
'form_submit'=>true,
];
$exports = [
'apis' => array (
0 => 'form',
1 => 'create',
2 => 'update',
),
'DataNodes' => array (
0 =>
array (
'type' => 'default',
),
),
'form_properties_array'=>[
'title'=> ['type'=>'text','tagName'=>'input',],
'id' => ['tagName'=>'input','type'=>'hidden'],
],
];
$this->run_parse_test($view, $expect, $exports, $as_is);
//////////
/// exec tests
//////////
$parser = new \Phad\DomParser();
$doc = new \Taeluf\PHTML($view);
$items = $parser->parse_doc($doc);
$item = $items[0];
$this->run_test_exec('?>'.$item['html_code'],
['Blog'=>(object)['title'=>'Good Title!']],
<<<HTML
<form action="" method="POST">
<input type="text" name="title" value="Good Title!">
</form>
HTML
);
// print_r($item);
}
public function testOn404_403(){
// this mistakenly compiles code when it really should just check that the response code ... code is found in the compiled item
$view = <<<HTML
<div item="Blog">
<p-data where="Blog.id = :id">
<on s=404><?php echo "404 error 1";?></on>
<on s=403><?php echo "403 error 1";?></on>
</p-data>
<p-data where="Blog.id = :id">
<on s=404><?php echo "404 error 2";?></on>
<on s=403><?php echo "403 error 2";?></on>
</p-data>
<h1 prop="title"></h1>
<p prop="intro"></p>
</div>
HTML;
$expect_html = <<<HTML
<div>
<h1><?=\$Blog->title?></h1>
<p><?=\$Blog->intro?></p>
</div>
HTML;
$compiler = new \Phad\TemplateCompiler();
$out = $compiler->compile($view, $this->file('code/template/main.php'));
//
// echo $out;
// exit;
$parser = new \Phad\DomParser();
$doc = new \Taeluf\PHTML($view);
$items = $parser->parse_doc($doc);
$item = $items[0];
// print_r($item);
$this->run_test_exec(
$item['code_for_response_403'],
['ItemInfo'=>'TestItem',
'TestItem'=>(object)['data_index'=>0]
],
'403 error 1',
);
$this->run_test_exec(
$item['code_for_response_403'],
['ItemInfo'=>'TestItem',
'TestItem'=>(object)['data_index'=>1]
],
'403 error 2',
);
$this->run_test_exec(
$item['code_for_response_404'],
['ItemInfo'=>'TestItem',
'TestItem'=>(object)['data_index'=>0]
],
'404 error 1',
);
$this->run_test_exec(
$item['code_for_response_404'],
['ItemInfo'=>'TestItem',
'TestItem'=>(object)['data_index'=>1]
],
'404 error 2',
);
$this->run_test_exec(
'?>'.$item['html_code'],
[ 'Blog'=>(object)['title'=>'puppies', 'intro'=>'are cute'], ],
<<<HTML
<div>
<h1>puppies</h1>
<p>are cute</p>
</div>
HTML
);
}
/**
* @test data nodes
* @test parsed output (where prop attributes are replaced by things like `<?=$Blog->title?>`
* @test execution of 404 response code
* @test execution of the main code (no compiler or phad object)
*/
public function testOn404(){
$view = <<<HTML
<div item="Blog">
<p-data where="Blog.id = :id">
<on s=404><?php echo "404 error";?></on>
</p-data>
<h1 prop="title"></h1>
<p prop="intro"></p>
</div>
HTML;
$expect_html = <<<HTML
<div>
<h1><?=\$Blog->title?></h1>
<p><?=\$Blog->intro?></p>
</div>
HTML;
// $compiler = new \Phad\TemplateCompiler();
// $out = $compiler->compile($view, $this->file('code/template/main.php'));
// //
// echo $out;
// exit;
$parser = new \Phad\DomParser();
$doc = new \Taeluf\PHTML($view);
$items = $parser->parse_doc($doc);
$var_exports =
[
'DataNodes'=>
[
['where'=>'Blog.id = :id', 'type'=>'node'],
['type'=>'default']
],
];
$items_data = [];
$this->run_parse_test($view, $expect_html, $var_exports, $items_data);
// print_r($items);
$item = $items[0];
$this->run_test_exec(
$item['code_for_response_404'],
['ItemInfo'=>'TestItem',
'TestItem'=>(object)['data_index'=>0],
],
'404 error'
);
$this->run_test_exec(
'?>'.$item['html_code'],
[ 'Blog'=>(object)['title'=>'puppies', 'intro'=>'are cute'], ],
<<<HTML
<div>
<h1>puppies</h1>
<p>are cute</p>
</div>
HTML
);
}
public function testNestedItem(){
// pre-compile the author view so we can insert the compiled author into the expected blog copmiled output ...
$compiler = new \Phad\TemplateCompiler();
$author_view = <<<HTML
<div item="Author">
<p-data where="Author.id = :id"></p-data>
<h1 prop="name"></h1>
<p prop="hometown"></p>
</div>
HTML;
// note the $author_view inserted into the blog template
$view = <<<HTML
<div item="Blog">
<p-data where="Blog.id = :id"></p-data>
<h1 prop="title"></h1>
<p prop="intro"></p>
{$author_view}
</div>
HTML;
$full = $compiler->compile($view, file_get_contents($this->file('code/template/main.php')));
$fully_compiled_Author = $compiler->compile($author_view, file_get_contents($this->file('code/template/main.php')));
// i have to remove the ROUTE_META & SITEMAP_META portions since i've made those be added to every compiled view ...
$fully_compiled_Author = explode('<?php', $fully_compiled_Author);
// get the code before ROUTE_META's <?php
$before = array_shift($fully_compiled_Author);
// remove the ROUTE_META block
array_shift($fully_compiled_Author);
// remove the SITEMAP_META block
array_shift($fully_compiled_Author);
// re-insert the code before ROUTE_META's <?php
array_unshift($fully_compiled_Author,$before);
// put it back together as a string
$fully_compiled_Author = implode('<?php', $fully_compiled_Author);
$target_html = [
//index 0 is author's html (just the item html, not the complicate template stuff)
<<<HTML
<div>
<h1><?=\$Author->name?></h1>
<p><?=\$Author->hometown?></p>
</div>
HTML,
//index 1 is the blog html which contains the fully compiled author src
$expect_html = <<<HTML
<div>
<h1><?=\$Blog->title?></h1>
<p><?=\$Blog->intro?></p>
$fully_compiled_Author
</div>
HTML,
];
$parser = new \Phad\DomParser();
$doc = new \Taeluf\PHTML($view);
$items = $parser->parse_doc($doc);
$var_exports =
[
//author comes first, bc it is a child node, so it gets compiled first
0=>// 'Author_nodes'=>
[
'DataNodes'=>[
['where'=>'Author.id = :id', 'type'=>'node'],
['type'=>'default'],
],
],
1=>// 'Blog_Nodes'=>
[
'DataNodes'=>
[
['where'=>'Blog.id = :id', 'type'=>'node'],
['type'=>'default']
],
],
];
foreach ($items as $index=>$item_data){
$exports = $var_exports[$index];
$exports = array_map(function($v){return var_export($v,true);}, $exports);
$this->test('compare lines');
$this->compare_lines($target_html[$index], $item_data['html_code']);
$this->test('compare data');
$this->compare_data($item_data, [], $var_exports[$index]);
}
// echo $doc;
$this->str_contains($doc,
'$BlogInfo = (object)[',
"'where' => 'Blog.id = :id'",
'<h1><?=$Blog->title?></h1>',
'$AuthorInfo = (object)[',
"'where' => 'Author.id = :id',",
'<p><?=$Author->hometown?></p>',
);
}
/**
* @test that doc output contains both items
* @test that parser creates the correct template_args
*/
public function testTwoItems(){
$view = <<<HTML
<div item="Blog">
<p-data where="Blog.id = :id"></p-data>
<h1 prop="title"></h1>
<p prop="intro"></p>
</div>
<div item="Author">
<p-data where="Author.id = :id"></p-data>
<h1 prop="name"></h1>
<p prop="hometown"></p>
</div>
HTML;
$compiler = new \Phad\TemplateCompiler();
$full = $compiler->compile($view, file_get_contents($this->file('code/template/main.php')));
// var_dump($full);
// exit;
$target_html = [
$expect_html = <<<HTML
<div>
<h1><?=\$Blog->title?></h1>
<p><?=\$Blog->intro?></p>
</div>
HTML,
<<<HTML
<div>
<h1><?=\$Author->name?></h1>
<p><?=\$Author->hometown?></p>
</div>
HTML,
];
$parser = new \Phad\DomParser();
$doc = new \Taeluf\PHTML($view);
$items = $parser->parse_doc($doc);
$var_exports =
[
0=>// 'Blog_Nodes'=>
[
'DataNodes'=>
[
['where'=>'Blog.id = :id', 'type'=>'node'],
['type'=>'default']
],
],
1=>// 'Author_nodes'=>
[
'DataNodes'=>[
['where'=>'Author.id = :id', 'type'=>'node'],
['type'=>'default'],
],
],
];
foreach ($items as $index=>$item_data){
$exports = $var_exports[$index];
$exports = array_map(function($v){return var_export($v,true);}, $exports);
$this->compare_lines($target_html[$index], $item_data['html_code']);
$this->compare_data($item_data, [], $var_exports[$index]);
}
echo $doc;
$this->str_contains($doc,
'$BlogInfo = (object)[',
"'where' => 'Blog.id = :id'",
'<h1><?=$Blog->title?></h1>',
'$AuthorInfo = (object)[',
"'where' => 'Author.id = :id',",
'<p><?=$Author->hometown?></p>',
);
}
public function testInnerLoop(){
$view = <<<HTML
<div item="Blog" loop="inner">
<h1 prop="title"></h1>
</div>
HTML;
$expect_html = <<<HTML
<h1><?=\$Blog->title?></h1>
HTML;
$var_exports =
[
'DataNodes'=>
[
['type'=>'default']
],
];
$items_data = [];
$doc = $this->run_parse_test($view, $expect_html, $var_exports, $items_data);
}
public function testDataItemClean(){
$view = <<<HTML
<div item="Blog">
<p-data where="Blog.id = :id"></p-data>
<h1 prop="title"></h1>
<p prop="intro"></p>
</div>
HTML;
$expect_html = <<<HTML
<div>
<h1><?=\$Blog->title?></h1>
<p><?=\$Blog->intro?></p>
</div>
HTML;
$var_exports =
[
'DataNodes'=>
[
['where'=>'Blog.id = :id', 'type'=>'node'],
['type'=>'default']
],
];
$items_data = [];
$this->run_parse_test($view, $expect_html, $var_exports, $items_data);
}
public function testDataItem(){
$parser = new \Phad\DomParser();
$view = <<<HTML
<div item="Blog">
<p-data where="Blog.id = :id"></p-data>
<h1 prop="title"></h1>
<p prop="intro"></p>
</div>
HTML;
$expect_html = <<<HTML
<div>
<h1><?=\$Blog->title?></h1>
<p><?=\$Blog->intro?></p>
</div>
HTML;
$doc = new \Taeluf\PHTML($view);
$items = $parser->parse_doc($doc);
$this->compare_lines(
$expect_html,
$items[0]['html_code']
);
unset($items[0]['html_code']);
$this->compare_arrays(
[
['item_name'=>'Blog',
'item_type'=>'view',
'apis'=>var_export(['view', 'data'], true),
'DataNodes'=>var_export(
[
['where'=>'Blog.id = :id', 'type'=>'node'],
['type'=>'default']
] ,true),
'ItemForeach' => 'Blog => $BlogRow',
'ItemName' => '$Blog',
'Item_Row' => '$BlogRow',
'ItemInfo' => '$BlogInfo',
'ItemInfoSubmitErrorsList' => 'BlogSubmitErrorsList',
'route_info' => '',
'sitemap_info' => '',
'form_properties' => '',
'form_is_candelete' => '',
// 'item_rows' => '',
// 'response_code_200' => '',
'status_codes'=>'',
'response_code_403' => '',
'response_code_404' => '',
'response_code_500' => '',
'form_submit' => '',
// I changed phad to ALWAYS print the can_read_row() code, so I lazily added this to make the test pass.
'can_read_row' => $items[0]['can_read_row'],//'',
// 'html_code' => trim($expect_html),
]
],
$items
);
}
public function testSimpleItem(){
$parser = new \Phad\DomParser();
$view = <<<HTML
<div item="Blog">
<h1 prop="title"></h1>
<p prop="intro"></p>
</div>
HTML;
$expect_html = <<<HTML
<div>
<h1><?=\$Blog->title?></h1>
<p><?=\$Blog->intro?></p>
</div>
HTML;
$doc = new \Taeluf\PHTML($view);
$items = $parser->parse_doc($doc);
$this->compare_arrays(
[
['item_name'=>'Blog',
'item_type'=>'view',
'apis'=>var_export(['view', 'data'], true),
'DataNodes'=>var_export([['type'=>'default']] ,true),
'ItemForeach' => 'Blog => $BlogRow',
'ItemName' => '$Blog',
'Item_Row' => '$BlogRow',
'ItemInfo' => '$BlogInfo',
'ItemInfoSubmitErrorsList'=> 'BlogSubmitErrorsList',
'route_info' => '',
'sitemap_info' => '',
'form_properties' => '',
'form_is_candelete' => '',
// 'item_rows' => '',
// 'response_code_200' => '',
'status_codes' => '',
'response_code_403' => '',
'response_code_404' => '',
'response_code_500' => '',
'form_submit' => '',
// I changed phad to ALWAYS print the can_read_row() code, so I lazily added this to make the test pass.
'can_read_row' => $items[0]['can_read_row'],
'html_code' => trim($expect_html),
]
],
$items
);
}
}