Quitting Smarty to do it manually

2020-04-02 07:05发布

问题:

I am facing the problem that I'm not really sure how to develop without a framework or a template engine. I started coding that way and now I want to go to basics.

I used to work with this MVC schema, using Codeigniter and Smarty as a template engine. What I want to do now is to use raw php without both tools mentioned.

I don't know how to "copy" the concept of Smarty's "block" and "extends".

I used to define a base.tpl file which had html head, only the body tag, and the base css and js files (the ones that are always used in every page of the site), like this: (snippet)

 <!DOCTYPE html>
 <head>
 <meta charset="utf-8" />
 <title>Dashboard</title>
 <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport" />
 <meta content="" name="description" />
 <meta content="" name="author" />

 <!-- ================== BEGIN BASE CSS STYLE ================== -->
 <link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
 <link href="{site_url()}assets/css/animate.min.css" rel="stylesheet" />
 <!-- ================== END BASE CSS STYLE ================== -->

 <!-- ================== BEGIN PAGE LEVEL CSS STYLE ================== -->
 {block name='custom_css'}{/block}
 <!-- ================== END PAGE LEVEL CSS STYLE ================== -->

 <!-- ================== BEGIN BASE JS ================== -->
 <script src="{site_url()}assets/plugins/pace/pace.min.js"></script>
 <!-- ================== END BASE JS ================== -->
</head>
<body>
  <div id="page-container" class="fade page-sidebar-fixed page-header-fixed">
    <div id="header" class="header navbar navbar-default navbar-fixed-top">
        <div class="container-fluid">
            {include file='base/header.tpl'}
        </div>
    </div>
    <!-- BEGIN PAGE -->
    <div class="page-content">
        <!-- BEGIN PAGE CONTAINER-->
        <div class="container-fluid">
            <!-- BEGIN PAGE HEADER-->
            <div class="row-fluid">
                <div class="span12">                        
                    <!-- BEGIN PAGE TITLE & BREADCRUMB-->
                    {include file='admin/base/breadcrumb.tpl'}
                    <!-- END PAGE TITLE & BREADCRUMB-->
                </div>
            </div>
            <!-- END PAGE HEADER-->
            {block name='content'}{/block}
        </div>
        <!-- END PAGE CONTAINER-->    
    </div>
    <!-- END PAGE -->

and then when I need to call this base.tpl I did this:

{extends file='base/base.tpl'}

{block name='custom_css}
   <link href="{site_url()}assets/css/pages/blog.css" rel="stylesheet" type="text/css"/>
 {/block}

{block name='content'}
   <div class="row">
      <div class="col-md-3 col-sm-6">
        <div class="widget widget-stats bg-green">
        <div class="stats-icon stats-icon-lg"><i class="fa fa-globe fa-fw"></i></div>
        <div class="stats-title">TODAY'S VISITS</div>
        <div class="stats-number">7,842,900</div>
        <div class="stats-progress progress">
            <div class="progress-bar" style="width: 70.1%;"></div>
        </div>
        <div class="stats-desc">Better than last week (70.1%)</div>
      </div>
   </div>

I have been searching but I am affraid I'm missing the right words to search because I am not finding answers.

I would like to be guided please!

回答1:

Another way could be to do something like this. I feel like this is probably closer to what a template engine ends up with, but with using the {block} syntax instead.

index.php

<?php
$blocks = array();
function add_block($name, $callback){
    global $blocks;
    ob_start();
    $callback();
    $output = ob_get_flush();
    $blocks[$name] = $output;
}

function get_block($name){
    global $blocks;
    if(!empty($blocks[$name])){
        return $blocks[$name];
    }
    return '';
}

//stop any errors from being output. 
ob_start();

//include the page code
include 'page.php';
$cleanup = ob_end_clean();

//now output the template
include 'template.php';

page.php

<?php

add_block('head', function(){
    ?><script type="text/javascript">/* Some JS */</script><?php
});

add_block('body', function(){
    ?><p>Some body text goes here</p><?php
});

template.php

<html>
    <head>
        <title>Site Title</title>
        <?php echo get_block('head'); ?>
    </head>
    <body>
        <?php echo get_block('body'); ?>
    </body>
    <?php echo get_block('after_body'); ?>
</html>


回答2:

I'm not sure how smarty does it and have actually never used a templating engine myself. But maybe this could be how it's done.

Say we have:

index.php
page.php
template.php

When we go to index.php it could start and output buffer and include page.php. After the include catch the output buffer and use some regular expressions to match and blocks found within it and put them into variables.

Now do the same for the template.php and find all the blocks in there too and replace the blocks with the blocks found in page.php.

I don't actually think that's how templating engines do it. But that's one possible way.



回答3:

A very tiny self made, templating engine based on regex replace using an array hook to "manually" parse templates

(but i grant you, smarty is more functional) To any question, feel free to answer me here

The regex match whole file's term like {term-_}, and replace it by a php condition who will be executed on at rendering time.

if a mark" doesn't found in $vars, it will simply replaced by an empty string

Engine

function parse($vars, $tpl)
{
    return preg_replace
    (
        '#\{([a-z0-9\-_]*?)\}#Ssie',
        '( ( isset($vars[\'\1\']) )
            ? $vars[\'\1\']
            : \'\'
        );',
        file_get_contents(TEMPLATE_DIR.$tpl)
    );
}

part of index

     <html>
          <head>...</head>
          {body}
     </html>

part of body

    <body>
         <div class='ui'>{username}</div>
    </body>

Usage

    <?php
    // include engine function
    define("TEMPLATE_DIR", __DIR__."templates/");
    require_once("library/parse.php");

    // just init $vars on top of index
    $vars = [];

    // and access and fill it anywhere
    $vars = ["username" => $_SESSION["user"]];

    // prepare the body including previous $vars declarations
    $vars["body"] = parse($vars, 'body');

    echo parse($vars, 'index');

Output

    <html>
         <head>...</head>
         <body>
             <div class='ui'>Stack Overflow :)</div>
         </body>
    </html>

You can improve it by using constant and prevent double wrap marker {{}} or more, or placing debug trace...

Add this to start of engine to prevent templates containing object bracket can be bad interpreted as a templates marker :

$out = file_get_contents(TEMPLATE_DIR.$tpl);
$out = str_replace("{{}}", "{}", $out);

To use constant, you can use perform as like :

    $empty = (DEBUG) ? "_EMPTY_" : "";
    return preg_replace
    (
        '#\{([a-z0-9\-_]*?)\}#Ssie', 
        '( ( isset($vars[\'\1\']) ) 
            ? $vars[\'\1\'] 
            : ( defined(\'_\'.strtoupper(\'\1\').\'_\') 
                ? constant(\'_\'.strtoupper(\'\1\').\'_\') 
                : $empty 
            ) 
        );',
        $out
    );

Note:

__DIR__ 

used in my code is valid for PHP >= 5.3.0 try but you can use

dirname(__FILE__)

For PHP < 5.3.0 try