Pass multiple parameters to a blade directive

前端 未结 9 1739
时光说笑
时光说笑 2021-02-19 07:31

I\'m trying to create a blade directive to highlight some words that will return from my search query.

This is my blade directive:

class AppServiceProvid         


        
相关标签:
9条回答
  • 2021-02-19 07:40
    Blade::directive('highlight', function($arguments){
    
            list($arg1, $arg2) = explode(',',str_replace(['(',')',' ', "'"], '', $arguments));
    
            $expressionValues = preg_split('/\s+/', $arg1);
    
            $output = "";
    
            foreach ($expressionValues as $value) {
                $output .= str_replace($value, "<b>".$value."</b>", $arg2);
            }
    
            return "<?php echo \"{$output}\"; ?>";
        });
    
    0 讨论(0)
  • 2021-02-19 07:43

    The value received on blade directive function is a sting, so, you must parse to get the values:

    BLADE

    @date($date, 'd-M-Y')

    AppServiceProvider

    Blade::directive('date', function ($str) {
      // $str = "$date, 'd-M-Y'";
      $data = explode(',',str_replace(' ', '', $str));
      //$data = ["$date", "'d-M-Y'"]
      $date = $data[0];
      $format = $data[1];
      return "<?= date_format(date_create($date), $format) ?>";
    });
    
    0 讨论(0)
  • 2021-02-19 07:45

    For associative arrays, eval() may be the easiest. But its use is adverted as dangerous, because it's like your opening a hole, a needle for code execution. In same time eval() execute at runtime, well it store the code to be executed in database (caching [well it mean it cache compiled byte code]). That's additional overhead, so performance will take a hit. Here's a nice paper on the topic [didn't read or get into the details]) https://link.springer.com/chapter/10.1007%2F978-981-10-3935-5_12.

    Well here I may have got you!, there is no performance difference at server serving performance, because views are cached, and generated only when you change them. Directives are translated to php code and in another process they are cached. (you can find the generated view in storage/framework/views)

    So for

    Blade::directive('custom', function ($expression) {
        eval("\$myarray = [$expression];");
    
        // do something with $myarray
        return "<?php echo ..";
    });
    

    It's just ok. There is nothing to talk about for eval() and performance (it's done and cached, and the generated php code is the one that will run over and over (just make sure the returned php code by the directive doesn't hold eval(), unless there is a reason). Using eval() directly (which will be used for different request over and over) will impact performance. (I wanted to talk about eval(), I think those are useful info)

    as it is we can parse array form ["sometin" => "i should be sting", "" => "", ...].

    eval("\$array = $expression;");
    // then we can do what we want with $array 
    

    However we can't pass variables. ex: @directive(["s" => $var]) if we use eval, $var will be undefined in the directive function scope. (don't forget that directive are just a way to generate tempalte beautifully, and turning the ugly (not really ugly) php code into such directive. In fact it's the inverse, we are turning the beautiful directive to the php code that will be executed at the end. And all you are doing here is generating, building, writing the expression that will form the final php pages or files.)

    What you can do instead is to pass the variable in this way ["s" => "$var"] , so it will pass through eval. And then in your return statement, use it example:

    return "<?php echo ".$array['s'].";?>";
    

    when the template will be generated this will be <?php echo $var;?>;

    Remember, if you decide to use eval, never use it within the returned string! or maybe you want to in some cases.

    Another solution

    (which is easy) along to the proposed parsing solutions, is to use a json format to passe data to your directive, and just use json_decode. (it just came to me)

    class AppServiceProvider extends ServiceProvider
    
    {
        public function boot()
        {
            Blade::directive('highlight', function($json_expression){
    
                $myArray = json_decode($json_expression)
    
                // do something with the array
            });
        }
    
        public function register()
        {
        }
    }
    

    Here an example where I needed to do so: the goal is to automate this

    @php
        $logo = !empty($logo) ? $logo : 'logo';
        $width = !empty($width) ? $width : 'logo';
        //...    // wait i will not always keep doing that ! h h
    @endphp // imaging we do that for all different number of view components ...
    

    and so I wrote this directive:

     public function boot()
        {
            Blade::directive('varSet', function ($expr) {
                $array = json_decode($expr, true);
    
                $p = '<?php ';
                foreach ($array as $key => $val) {
                    if (is_string($val)) {
                        $p .= "\$$key = isset(\$$key) && !empty(\$$key) ? \$$key : '$val'; ";
                    } else {
                        $p .= "\$$key = isset(\$$key) && !empty(\$$key) ? \$$key : $val; ";
                    }
                }
                $p .= '?>';
    
                return $p;
            });
        }
    

    We use it like this:

    @varSet({
        "logo": "logo",
        "width": 78,
        "height": 22
    })// hi my cool directive. that's slick.
    

    Why this form work? it get passed as a string template like this

    """
    {\n
        "logo": "logo",\n
        "width": 78,\n
        "height": 22\n
    }
    """
    

    For using in template variable pass them as string like that "$var", same as what we did with eval.

    For parsing from ["" => "", ..] format may be eval() is the best choice. Remember that this is done at template generation which are cached later, and not updated, until we make change again. And remember to not use eval() within the return ; directive instruction. (only if your application need that)

    for just multi arguments, and so not an array: A function like that will do the job:

     public static function parseMultipleArgs($expression)
    {
        return collect(explode(',', $expression))->map(function ($item) {
            return trim($item);
        });
    }
    

    or

    public static function parseMultipleArgs($expression)
        {
            $ar = explode(',', $expression);
            $l = len($ar);
    
            if($l == 1) return $ar[0];
    
            for($i = 0; $i < $l; $i++){$ar[$i] = trim($ar[$i])}
    
            return $ar;
        }
    

    and you can tweak them as you like, using str_replace to remove things like () ...etc [in short we workout our own parsing. RegEx can be helpful. And depend on what we want to achieve.

    All the above are way to parse entries and separate them into variables you use for generating the template. And so for making your return statement.

    WHAT IF ALL YOU WANT IS TO HAVE YOUR DIRECTIVE TAKE AN ARRAY WITH VARIABLES FROM THE VIEW SCOPE:

    like in @section('', ["var" => $varValue])

    Well here particulary we use the multi arguments parsing, then we recover ["" => ..] expression separately (and here is not the point).

    The point is when you want to pass an array to be used in your code (view scope). You just use it as it is. (it can be confusing).

    ex:

    Blade::directive("do", function ($expr) {
        return "<?php someFunctionFromMyGlobalOrViewScopThatTakeArrayAsParameter($expr); ?>
    });
    

    This will evaluate to

    <?php someFunctionFromMyGlobalOrViewScopThatTakeArrayAsParameter(["name" => $user->name, .......]); ?>
    

    And so all will work all right. I took an example where we use a function, you can put all a logic. Directives are just a way to write view in a more beautiful way. Also it allow for pre-view processing and generation. Quiet nice.

    0 讨论(0)
  • 2021-02-19 07:47

    The best way to accomplish this task is exactly as @Everrett suggested.

    I checked through the blade code, and this is exactly how the laravel team has to do it as well.

    If you look through vendor/laravel/framework/src/Illuminate/View/Compilers/Concerns/CompilesHelpers.php, at the compileDd function, you'll notice that they use $arguments instead of $expression like they do in all of the other compile functions found in vendor/laravel/framework/src/Illuminate/View/Compilers/Concerns/

    // CompilesHelpers.php
    protected function compileDd($arguments)
    {
        return "<?php dd{$arguments}; ?>";
    }
    
    //CompilesConditionals.php
    protected function compileIf($expression)
    {
        return "<?php if{$expression}: ?>";
    }
    

    And if you look at vendor/symfony/var-dumper/Resources/functions/dump.php you'll see that Laravel handles variable arguments with ... splat notation in the dd function.

    if (!function_exists('dd')) {
        function dd(...$vars)
        {
    }}
    

    So you could do a directive like: (I put my custom function in app\helpers) If you do the same, you need to make sure to escape the backslashes.

    Blade::directive('customFunc', function ($expression) {
        return "<?php \\App\\Helpers\\customFunc({$arguments}); ?>";
    });
    

    and a custom function like:

    /**
     * Custom function to demonstrate usage
     * @param mixed ...$args
     * @return void
     */
    function customFunc(...$args): void {
        // Extract variables //
        // Use pad to get expected args, and set unset to null //
        list($arg1, $arg2, $arg3) = array_pad($args, 3, null);
    
        // Echo out args //
        echo "arg1: ${arg1} | arg2: ${arg2} | arg3: {$arg3}";
    }
    

    run php artisan view:clear

    And then use the directive:

    <div>
        @customFunc('hello','wonderful', 'world')
    </div>
    
    // Returns:
    arg1: hello | arg2: wonderful | arg3: world
    
    // Using
    <div>
        @customFunc('hello', 'world')
    </div>
    // Returns:
    arg1: hello | arg2: world | arg3:
    

    The best reason to do it this way is so that if your function evolves or changes, you only need to modify the underlining function. You wont have to clear views every time you change the code.

    0 讨论(0)
  • 2021-02-19 07:49

    If you want to reference variables within a custom blade directive you may not need to pass them directly to the directive. I solved this problem by calling the blade directive from within a blade component. Blade components have local variable scope and so you can simply pass all the variables you need within the call to the blade component (without polluting your view scope). This is sufficient so long as you don't actually need to modify the variables or use them for control logic in your directive.

    //view.blade.php
    @component('my-component',['myVar1'=> $something, 'myVar2'=>$somethingElse])
    @endcomponent
    
    //my-component.blade.php
    @myBladeDirective('Two variables accessible')
    
    //Boot method of relevant service provider
    Blade::directive('myBladeDirective', function ($someVar) {
        return "<?php echo $someVar : {$myVar1} and {$myVar2};?>
    });
    
    0 讨论(0)
  • 2021-02-19 07:52

    I found an alternative approach to accessing View variables within a Blade Directive.

    I wanted to check whether a given string appeared as an array key in a variable accessible in the view scope.

    As the Blade Directive returns PHP which is evaluated later, it is possible to 'trick' the Directive by breaking up a variable name so that it doesn't try to parse it.

    For example:

        Blade::directive('getElementProps', function ($elementID) {
            return "<?php
                // Reference the $elementData variable
                // by splitting its name so it's not parsed here
                if (array_key_exists($elementID, $" . "elementData)) {
                    echo $" . "elementData[$elementID];
                }
            ?>";
        }); 
    

    In this example we have split the $elementData variable name so the Blade Directive treats it like a spring. When the concatenated string is returned to the blade it will be evaluated as the variable.

    0 讨论(0)
提交回复
热议问题