0

I'm using the PHP HereDoc and NowDoc statements to build the web-pages in my web-site, with HereDocs for parts that need PHP variable values substituted into the web-pages, and NowDocs for parts that use the $ characters to specify jQuery statements and jQuery Objects variables in my JavaScript. However, this makes my HTML, CSS, and JavaScript/jQuery hard to read and maintain.

To get around this I thought I'd write a replacement function to perform the PHP variable substitution that HereDoc statements do, but for variables who's values were assigned with string expressions or by NowDoc statements. This way the PHP variables specified by ${variable-name} within the string would be substituted with their values and the jQuery statements and $ prefixed variables who's values are expected to be jQuery Objects are not substituted for PHP variables, what otherwise generally cause a PHP compiler error when there isn't a PHP variable with the name matching the jQuery statement or variable name or a runtime logic error when there is a name match.

Here is my code and a test NowDoc that:

  • assigns a long string containing the ${test} PHP variable to an element in a PHP array variable, and then
  • passes the array as a parameter to my NowHereDoc function to perform the PHP variable/value substitutions.

However, when I run the following code to build my web-page, the $test PHP variable isn't visible inside of the function and a NULL is substituted instead of the desired value.

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.0/jquery.min.js"></script>

<?php
//
// PHP
//

function NowHereDoc( &$_array_ ) { // By Ref array parameter minimizes data movement when calling this function.
    $_l_ = count( $_array_ );
    $_s_ = $_array_[ $_l_ - 1 ]; // Get the last element's string value.

    // Find each ${...} and substitute the variable's value ...

    $_i1_ = 0;
    while( ( $_i1_ = strpos( $_s_, '${', $_i1_ ) ) !== FALSE ) { // Get index of start of a variable name specified
                                                                 //  by ${...} or FALSE when there aren't any {more},
                                                                 // then stop looping.

        $_i2_  = strpos( $_s_, '}', $_i1_ );                     // Get index of end of the ${...} just found.
        $_l_   = $_i2_ - $_i1_ + 1;                              // Get length of ${...}.
        $_var_ = substr( $_s_, $_i1_, $_l_ );                    // Get the variable's name as a string.
        $_v_   = str_replace( [ '{', '}' ], '', $_var_ );        // Remove { and } from the variable name.
        $_val_ = "$_v_";                                         // Get the value of the specified variable -- this
                                                                 // doesn't find the variable, instead returns NULL!

        // Substitute the variable's value into the original string, $_s_

        // $_s_ = substr_replace( $_s_, $$_var_, $_i1_, $_l_ );  // Replace the single occurance of the variable
                                                                 // with its value. This could to replace occurances
                                                                 // not with in comments, not yet implemented in
                                                                 // function.

         $_s_   = str_replace( $_var_, $_val_, $_s_ );           // Replace all occurances of the variable with its
                                                                 // value.

    } // End of while( ( $_i1_ = strpos( $_s_, '${', $_i1_ ) ) !== FALSE ) ...

    $_array_[ $_l_ - 1 ] = $_s_;                                 // Set the last element's string value to the
                                                                 // updated string in $_s_.

}

// No Variable substitutions allowed in a NowDoc.
$strMAINs[] = <<<'MAIN'
<!-- ======================================================
======================================================
=
= HTML - ${test} - This should be substituted with hi
=
======================================================
====================================================== -->
<p>to be set by this following script</p>
<script id="scriptId">
  //
  // JavaScript!
  //
  // The following jQuery statement shouldn't be changed.
  //

  var $jQVar = $( 'p' ).html( 'there' );
</script>
MAIN;

//
// PHP
//

$test = 'hi';

NowHereDoc( $strMAINs );
?>

<!-- HTML -->
<b>Test of my PHP NowHereDoc function</b>

How can I access PHP variables by their names specified as string values, which are global or local within a function calling the NowHereDoc function from within it without having to pass them in as parameters or specify them as globals variables in the function? I remember seeing a PHP library function that could return a value given its name as a string, but I don't remember the function's name.

Thanks

Matt
  • 1,073
  • 1
  • 8
  • 14
  • 1
    Sounds rather dreadful to begin with. Any particular reason why you can not just write HTML, and insert PHP tags only where needed? https://www.php.net/manual/en/language.basic-syntax.phpmode.php – CBroe Nov 10 '20 at 09:39
  • I've left an answer for this specific query, but I agree with @CBroe that this probably isn't the best approach – Matt Nov 10 '20 at 09:40
  • 2
    (Should the reason you are attempting this be that you need templates to be modified by non-developers - then maybe rather go with an existing templating solution, than trying to roll your own.) – CBroe Nov 10 '20 at 09:40
  • I use Symfony v2.8, which uses PHP code to create the HTML/CSS/JavaScript(and jQuery) content in strings or echo statements, but I use HereDocs and NowDocs statements so that the web-page contain is easier to read and maintain, and string expressions for short programmatically controlled web-page segments. Here, I showed the tag in my example to make it clearer that I was using PHP to inject content into the web-page. What's in the PHP tag here, is generally what's in my server-side PHP script. The actual HTML content is stored in PHP string variables, that are sent the browser. –  Nov 10 '20 at 10:13
  • Have you tried using the templating included in Symfony? https://symfony.com/doc/2.8/templating.html – M. Eriksson Nov 10 '20 at 10:34

2 Answers2

0

To import a global variables into your function you can do this:

// global scope
$test = 'my string';

function myFunction() {
    global $test;
}

Or if you have an anonymous function you can pull in variables from the parent scope like this:

function myOuterFn() {
    $test = 'my string';
    $myInnerFn = function() use ($test) {
        echo $test;
    };
}

In response to your comment, if you looped through your content to extract any referenced variables into an array of var names, you could them loop through that array and import them from global as demonstrated in this example: https://3v4l.org/rH8J0

Matt
  • 1,073
  • 1
  • 8
  • 14
  • Thanks all. However, as per using global or use, those would make my NowHereDoc more specific to the call, I was hoping to keep it general enough to make several calls throughout my PHP script and contain larger web-page, less broken-up, segments specified in NowDoc statements, and then calling my NowHereDoc function to resolve any tPHP variables contained in the segment. Using parameters, would be more dynamic if I used the variable parameter mechanism to support multiple parameters, but I thought I saw a way to access the parent variable environment at each NowHereDoc function call. –  Nov 10 '20 at 10:26
  • I added an additional example at the bottom which might help with your use case – Matt Nov 10 '20 at 10:41
  • Matt, that does help, but what about the variables that are defined in a function that includes a php file? These variables would be local to the function, right, and the globals statement you're showing wouldn't see them, right? I'd have to make all of those variables global, so that they could be seen by my NowHereDoc function with your globals suggestion. Since my NowHereDoc function is a nested function in an require statement within another function, is there a way to inherit the parent's variables? –  Nov 10 '20 at 11:10
  • Well the global keyword works both ways so you can declare vars as global in the fn and they will be put in global namespace: https://3v4l.org/O0soh – Matt Nov 10 '20 at 11:25
  • So you could have your fn define the replacement vars as global then import them in your NowHereDoc fn. I just want to echo again what various people have said in the comments on the question, you'll probably find a lot less issues in the long run by using an established templating engine such as the one included with Symfony – Matt Nov 10 '20 at 11:26
  • Thank you, I'll look at the Symfony templating engine. –  Nov 10 '20 at 12:00
0

While not a template solution, which may come later, I found the get_defined_vars() function, that was the piece I remembered seeing, but couldn't remember its name. It gets a list of the defined variables that are in-scope at the time it is called. Alternatively, a manual list of variables could be created and passed into the function, since most of the variables in the defined variables list returned by the get_define_vars() functions are not needed.

function NowHereDoc(  &$_vars_, &$_array_ ) { // a by-ref array of variables and their values from the caller's scope.
                                              //
                                              // a by-ref array specifying the web-content as a string that
                                              // contains one or more PHP variables in the ${...} format who's
                                              // values are to be substituted for the variable name.
                                              //
                                              //  & By-Ref prefix minimizes data movement when calling this function.
                                              //

    $_l_ = count( $_array_ );
    $_s_ = $_array_[ $_l_ - 1 ]; // Get the last element's string value.

    // Find each ${...} and substitute the variable's value ...

    $_i1_ = 0;
    while( ( $_i1_ = strpos( $_s_, '${', $_i1_ ) ) !== FALSE ) { // Get index of start of a variable name specified
                                                                 //  by ${...} or FALSE when there aren't any {more},
                                                                 // then stop looping.

        $_i2_  = strpos( $_s_, '}', $_i1_ );                     // Get index of end of the ${...} just found.
        $_l_   = $_i2_ - $_i1_ + 1;                              // Get length of ${...}.
        $_var_ = substr( $_s_, $_i1_, $_l_ );                    // Get the variable's name as a string.
        $_v_   = str_replace( [ '$', '{', '}' ], '', $_var_ );   // Remove { and } from the variable name.
        $_val_ = $_vars_[ $_v_ ];                                // Get the value of the specified variable.

        // Substitute the variable's value into the original string, $_s_

        // $_s_ = substr_replace( $_s_, $_val_, $_i1_, $_l_ );  // Replace the single occurance of the variable
                                                                 // with its value. This could to replace occurances
                                                                 // not with in comments, not yet implemented in
                                                                 // function.

        $_s_   = str_replace( $_var_, $_val_, $_s_ );            // Replace all occurances of the variable with its
                                                                 // value.

    } // End of while( ( $_i1_ = strpos( $_s_, '${', $_i1_ ) ) !== FALSE ) ...

    $_array_[ $_l_ - 1 ] = $_s_;                                 // Set the last element's string value to the
                                                                 // updated string in $_s_.

}

// No variable substitutions.
$strMAINs[] = <<<'MAIN'
<!-- 
=======================================================================
=======================================================================                     
=
= ${test1} ${test2} // ${test1} ${test2} should be substituted with hi there
=          
=======================================================================          
=======================================================================
-->
MAIN;

$test1 = 'hi';
$test2 = 'there';

$vars = get_defined_vars();
NowHereDoc( $vars, $strMAINs );

Again, thanks for the help, Matt and CBroe.