Giter Club home page Giter Club logo

Comments (7)

reinink avatar reinink commented on August 23, 2024

I have found a potential issue when using League\Plates\Variable methods on an object. It's possible that methods can collide. Consider this class:

<?php

class HTML
{
    public function css($url)
    {
        return '<link rel="stylesheet" href="' . $url . '" />';
    }
}

You may potentially use this in a template like this:

<html>
<head>
    <?=$html->css('all.css')->raw()?>
<head>
</html

However, since the method css() exists on the League\Plates\Variable class as well, the $html method will never be called. The simple answer here is to simply check if the object has a method called "css", and if so call it. Like so:

public function css()
{
    if (method_exists($this->variable, 'css')) {
        return new self(call_user_func_array([$this->variable, 'css'], func_get_args()));
    }

    return css_escaping_function($this->variable);
}

While this works great for defined methods (like in our example), it doesn't work for methods made available through an object's magic __call() method. The function method_exists() will always return false. There is really no proper way to determine if the __call() method accepts or rejects any particular method. The best "hack" I can come up with is to simply determine if a __call() magic method exists, and try to call the method. If an exception is thrown, it obviously doesn't exist. Something like this:

<?php

public function css()
{
    if (method_exists($this->variable, 'css')) {
        return new self(call_user_func_array([$this->variable, 'css'], func_get_args()));
    } elseif (!method_exists($this->variable, 'css') and is_callable([$this->variable, 'css'])) {
        try {

            // Try to call the method
            $response = call_user_func_array([$this->variable, 'css'], func_get_args());

            // Success method call, this must be a valid method
            // Return the value is the response isn't null or false
            if (!in_array($response, array(null, false))) {
                return new self($response);
            }

        } catch (\Exception $e) {
            // An exception was thrown, clearly the method doesn't exist
            // Just ignore
        }
    }

    return css_escaping_function($this->variable);
}

Is this an acceptable solution to the problem, or is there a better way?

from plates.

reinink avatar reinink commented on August 23, 2024

So, the next big challenge I see is preventing double escaping. Unlike the compiled approach where variables are only escaped on output, this method will have variables triggering __toString() anytime a string operation is performed, and that may not always be ideal.

Double escaping example

No escaping issue:

// $city->name = '<strong>Toronto</strong>'
// Outputs: <STRONG>TORONTO</STRONG>

<?=$this->batch($city->name, 'strtoupper')?>

Double escaping issue due to string concatenation:

// $city->name = '<strong>Toronto</strong>'
// $city->province = 'Ontario'
// Outputs: &LT;STRONG&GT;TORONTO&LT;/STRONG&GT; (ONTARIO)

<?=$this->batch($city->name . ' (' . $city->province . ')', 'strtoupper')?>

Fixed double escaping issue by outputting function response with raw():

// $city->name = '<strong>Toronto</strong>'
// $city->province = 'Ontario'
// Outputs: <STRONG>TORONTO</STRONG> (ONTARIO)

<?=$this->batch($city->name . ' (' . $city->province . ')', 'strtoupper')->raw()?>

Fixed double escaping issue by adding raw() prior to string concatenation:

// $city->name = '<strong>Toronto</strong>'
// $city->province = 'Ontario'
// Outputs: <STRONG>TORONTO</STRONG> (ONTARIO)

<?=$this->batch($city->name->raw() . ' (' . $city->province->raw() . ')', 'strtoupper')?>

Things to consider:

  • Should extension functions be responsible for escaping? For example, they could automatically return their results as an instance of League\Plates\Variable.
  • Any time two variables are joined (ie. <?=$user->first_name . ' ' . $user->last_name?>) they will be escaped using the HTML approach.
  • Often times it's preferred that escaping happens after template functions are applied. However, passing a template variable to a function will trigger __toString().
  • It may be possible to have template functions automatically get the raw() value of a template variable before manipulating it by detecting if it's variable type the class name

from plates.

stefankleff avatar stefankleff commented on August 23, 2024
  1. I personally don't like the method_exists() approach for preventing naming collisions. But this project is not the first one using proxies. The common approach is prefixing custom methods with a double underscore (like PHP itself does with magic functions).
  2. Why does passing a template var to a function trigger __toString() ? That shouldn't be the case unless you cast it to string.

from plates.

reinink avatar reinink commented on August 23, 2024

I personally don't like the method_exists() approach for preventing naming collisions. But this project is not the first one using proxies. The common approach is prefixing custom methods with a double underscore (like PHP itself does with magic functions).

Right, but technically double underscored functions are reserved by PHP. Also, that still doesn't prevent collisions, they're still possible, so a check is still required. Finally, that would really make the API a lot uglier: <?$name->__css()?>.

Why does passing a template var to a function trigger __toString() ? That shouldn't be the case unless you cast it to string.

True, the passing of the variable doesn't trigger it. What I meant was that since all template functions modified strings, that __toString() will be triggered, and it's something that has to be considered, especially around preventing double escaping.

from plates.

shadowhand avatar shadowhand commented on August 23, 2024

Originally seen on #23...

https://gist.github.com/shadowhand/039d72433aa262f10b91

This is (roughly) an implementation of automatic escaping in views. It's incomplete, but should be enough to show what I mean about a read-only mode that triggers automatic escaping.

from plates.

reinink avatar reinink commented on August 23, 2024

@shadowhand If I understand your example correctly, it doesn't take into account multidimensional arrays or objects.

from plates.

reinink avatar reinink commented on August 23, 2024

Having been pretty excited about the possibilities here yesterday, today I find myself asking if this approach is worth all the complexity. I don't love the idea of working with proxied variable objects—I'd rather just work directly with the strings, arrays or objects I passed to the template. I'm also nervous about how annoying the double-escaping issue could become. Developers will always have to be thinking "is this going to cause a double/triple escape?". And the object method conflict issue is real, and who knows how many more issues we might stumble upon.

My original goal was to find a very good solution to automatic escaping—one that works for strings, arrays and objects, including any variations nested within these. While this approach works, it really is a pretty big hack of the language for the sole purpose of automatic escaping. Does the end justify the means?

For me, I keep going back to the proposed compiler (#23). To me this is a much more elegant solution to the problem. I think the reason it works so well is because it's actually solving the escaping problem in the right place—at the point of output. I know developers who use native PHP templates hate the idea of compiling, but the performance impact here is seriously negligible. And just because the templates are compiled, doesn't mean they are not PHP templates!

Is it really that smart to manipulate PHP this much just to avoid a compiling stage that actually provides the solution to the problem in a much cleaner way? Is it okay to use PHP to create hundreds or thousands of new objects, when we could just use PHP to "inject" the escape functions right where they're needed? Some people have said compiling introduces too much magic. I can tell you right now, this approach uses a lot more magic than that!

I like PHP templates because their PHP. No new syntax to learn. I know the functions available to me, including those I've developed. My editor has syntax highlighting and auto-completing built in. It's what I know. Personally, I really don't care if the template engine has to so some compiling in the background IF that means it's going to be safer and quicker to develop with.

from plates.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.