Giter Site home page Giter Site logo

php-view's Introduction

Build Status

PHP Renderer

This is a renderer for rendering PHP view scripts into a PSR-7 Response object. It works well with Slim Framework 4.

Cross-site scripting (XSS) risks

Note that PHP-View has no built-in mitigation from XSS attacks. It is the developer's responsibility to use htmlspecialchars() or a component like laminas-escaper. Alternatively, consider Twig-View.

Installation

Install with Composer:

composer require slim/php-view

Usage with Slim 4

use Slim\Views\PhpRenderer;

include "vendor/autoload.php";

$app = Slim\AppFactory::create();

$app->get('/hello/{name}', function ($request, $response, $args) {
    $renderer = new PhpRenderer('path/to/templates');
    return $renderer->render($response, "hello.php", $args);
});

$app->run();

Note that you could place the PhpRenderer instantiation within your DI Container.

Usage with any PSR-7 Project

//Construct the View
$phpView = new PhpRenderer("path/to/templates");

//Render a Template
$response = $phpView->render(new Response(), "hello.php", $yourData);

Template Variables

You can now add variables to your renderer that will be available to all templates you render.

// via the constructor
$templateVariables = [
    "title" => "Title"
];
$phpView = new PhpRenderer("path/to/templates", $templateVariables);

// or setter
$phpView->setAttributes($templateVariables);

// or individually
$phpView->addAttribute($key, $value);

Data passed in via ->render() takes precedence over attributes.

$templateVariables = [
    "title" => "Title"
];
$phpView = new PhpRenderer("path/to/templates", $templateVariables);

//...

$phpView->render($response, $template, [
    "title" => "My Title"
]);
// In the view above, the $title will be "My Title" and not "Title"

Sub-templates

Inside your templates you may use $this to refer to the PhpRenderer object to render sub-templates. If using a layout the fetch() method can be used instead of render() to avoid appling the layout to the sub-template.

<?=$this->fetch('./path/to/partial.phtml', ["name" => "John"])?>

Rendering in Layouts

You can now render view in another views called layouts, this allows you to compose modular view templates and help keep your views DRY.

Create your layout path/to/templates/layout.php.

<html><head><title><?=$title?></title></head><body><?=$content?></body></html>

Create your view template path/to/templates/hello.php.

Hello <?=$name?>!

Rendering in your code.

$phpView = new PhpRenderer("path/to/templates", ["title" => "My App"]);
$phpView->setLayout("layout.php");

//...

$phpview->render($response, "hello.php", ["title" => "Hello - My App", "name" => "John"]);

Response will be

<html><head><title>Hello - My App</title></head><body>Hello John!</body></html>

Please note, the $content is special variable used inside layouts to render the wrapped view and should not be set in your view paramaters.

Exceptions

\RuntimeException - if template does not exist

\InvalidArgumentException - if $data contains 'template'

php-view's People

Contributors

akrabat avatar ayesh avatar dependabot[bot] avatar dopesong avatar geggleto avatar jakejohns avatar jaywilliams avatar jeremyvignelles avatar l0gicgate avatar llvdl avatar nbayramberdiyev avatar odan avatar piotr-cz avatar rotexdegba avatar sasezaki avatar silentworks avatar tsu1980 avatar twogood avatar vlakoff avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

php-view's Issues

Sub-templates and layouts

From the documentation:

Inside your templates you may use $this to refer to the PhpRenderer object to render sub-templates.

Including sub-templates (with fetch) when you are using layouts is problematic as sub-template content also includes the surrounding layout. Using a straight php include(template.file) is an effective workaround.

response leaking into view

When directly including a file, all scope variables are leaking so in effect it's possible to modify the $response and $this variables from within the template.

I think there could be an anonymous function inside the render method with own scope:

$render = function() {
    extract(func_get_arg(1));
    return include func_get_arg(0);
};

ob_start();
$render($this->templatePath . $template, $data);
$output = ob_get_clean();

source

Need consistent naming to fix autoloading

As it stands right now PHP-View doesn't work because the Composer autoloader doesn't match a PhpRenderer class name against a PHPRenderer.php file name.

One of two things needs to happen:

  • Change the filename from PHPRenderer.php to PhpRenderer.php
  • Change the class name from PhpRenderer to PHPRenderer

Both of these approaches fixed autoloading for me. I suspect the first option would be preferred, but I'd be fine with either one since I'm more concerned about it working than the naming style.

Rendering sub-templates?

How would you render sub-templates when working with PHP-View? For example, this simple code is not working ("Content" gets outputted last, after the header/footer templates)

$app->get('/', function (Request $request, Response $response) {
    return $this->renderer->render($response, "/pages/main.php");
});

//In main.php
<?php $this->render($response, "/header.php", []); ?>
Content
<?php $this->render($response, "/footer.php", []); ?>

I do understand why it's not working (Only one response object), but not how to fix it in a proper way?

Handling 404 errors

When a non-existent template is requested, PHP-View throws a generic RuntimeException. Is it possible to include some additional info in it (errorcode or something alike) to distinguish between 404 errors and other runtime exceptions?

PHP 8

Can you update composer.json for PHP 8? Thanks.

$_SESSION global variable is not available when rendering $content

How to reproduce:

$_SESSION['user_id'] = 'some_value';

$phpView = new PhpRenderer("path/to/templates", ["title" => "My App"]);
$phpView->setLayout("layout.php");

//...

$phpview->render($response, "hello.php", ["title" => "Hello - My App", "name" => "John"]);

In hello.php:

<?php var_dump($_SESSION); // Will not be set ?>
Hello <?=$name?>! Your id is: <?= $_SESSION['user_id'] ?>

In layout.php:

<?php var_dump($_SESSION); // Will be filled as intended ?>
<html><head><title><?=$title?></title></head><body><?=$content?></body></html>

So, in both hello.php and layout.php you will be able to get $title var. But, in hello.php global $_SESSION var will be empty.
Expected behavior would be leaving global variables intact.

Loop

How can I make a loop in html to display an array?

Unable to flush/reset reset body when calling multiple render()

I created a slim4 action to send more than one email. The two emails get their output from views and sends two separate emails.

The first email is sent ok, But the second one is sent with the body of the first and second (The recipient and subject is correct.)

I have excluded the email library by debugging and tracing the values being received. I can confirm the same response body is being set,

I tried making a copy of $response. That did not change things.

The code below is a summary

public function actionContact(RequestInterface $request, ResponseInterface $response, array $args): ResponseInterface

   $form_data = (array)$request->getParsedBody();

   $notify_view_ = $this->renderer->render(
      $response,
      'emails/contactus_notify.php',
      ['form_data' => $form_data]
   );
   $notify_message = $response->getBody();
   $notify_mail = new Email('[email protected], 'Subject 1', $notify_message)->send();
  
   // Send second email
   $thankyou_view_ = $this->renderer->render(
      $response,
      'emails/contactus_thankyou.php',
      ['form_data' => $form_data]
   );
   $thankyou_message = $response->getBody();
   $thankyou_mail = new Email('[email protected], 'Subject 2', $thankyou_message)->send();
}

I figured after further debugging that the second getBody() was actually concatenating the results of the two render()s.

I tried resetting the body

$response->getBody()->write('');

but this had no effect. The second email is the concatenation of the two writes by render.

Inserting a path to a named route in a php-view template?

As far as I can tell, there is no (clean) way to insert a path to a named route, or am I missing something? I have create a workaround which I am happy to contribute, but since I only started using the Slim Framework yesterday I want to make sure I am not missing something.

Ability to render partial view, output as string instead of directly to stream

Not so much a bug but a suggestion...

Slim2's view class had 2 methods available ( and 1 private ) in its view class. One allowed you to return a rendered template as a string, the other does what PHP-View render() effectively does.

// Slim2
public function fetch($template, $data = null) // returns rendered partial as string
public function display($template, $data = null) // outputs to screen

protected function render($template, $data = null) // does the parsing and passes to fetch/display

PHP-View only has one option available

// Slim3
public function render(ResponseInterface $response, $template, array $data = [])

The problem with the new implementation is that I cannot render and store partials as strings to be used in other parts of the app. Before I create my own implementation thats 99% similar, I wanted to chime in to see if this feature would be added to PHP-Views.

For example there is no way for me to do this, load up a few partials "alert_view" and "content_view" and then use an "empty_wrapper"

        $view['alert_view'] = $this->view->fetch('modules/alert_view.php', $view);
        $view['content_view'] = $this->view->fetch('login_view.php', $view);
        $this->view->display('wrappers/wrapper_empty_view.php', $view);

Since making the interface like Slim2 is probably not an option as you are using render() as Slim2 display(). Maybe create a new method named:

getPartial, partial, renderPartial?

that will allow you to get the output of a rendered template as a string.

Rendering templates from a sub-directory

Is this possible?

I have a template in a sub directory (within the template directory) but ?php include('header.phtml'); is not working, neither does it if I put slash / (root?) in front.

With Twig I could do;

{% include('header.php') %}

And that 'just worked' wherever the files was from the root of the template dir down.

thanks,

David,

the reserved $template key in data array

Hi,

Nice piece of code.
Juste 2 questions :

  1. why do you keep a check on the "$template" key present in the data array, while in this variable is now protected because the function protectedIncludeScope use func_get_arg(0) so there is no risk to overwrite it with the extract() function.

I think this check is useless :
https://github.com/slimphp/PHP-View/blob/master/src/PhpRenderer.php#L149

Am i missing something ????

2a) Why not use a require instead of an include ? there is already a check if the file exist, so using require() is pretty safe from my point of view.

2b) Are you sure there could have a throw exception/throwable from the extract() function ? are the try/catch really usefull ?

Thank you for your clarifications. Keep up the good work.

PhpRenderer::render() manipulates input response

Haven't tested it, but to me it seems as if the render method work on the input response itself instead of cloning it (as described here).

Response objects are immutable which means that the call to render() won’t update the response object; instead it will return us a new object which is why it needs to be captured like this. This is always true when you operate on the response object.

Return Json

Como retornar o header application/json.

Tentei
header('Content-Type: application/json');
$response->withHeader('Content-Type', 'application/json');

Porem sem sucesso

Slim 4

How to implement php-view in slim 4 using php-di?

QUESTION

Hello,

I was wondering why the render method has a ResponseInterfaceparameter.
Wouldn't it be better to write to the body inside the route-matching middleware:

$response->getBody()->write(
    $this->view->render('someTemplate.phtml', $someData)
);

and have the PhpRenderer just do 1 job: pass data into a template and return the resulting string?
For instance, what if i just need to render a template for an html email?
Doesn't this also violate the SRP? In my opinion a view renderer shouldn't have the responsibility of filling a response.

thanks and kind regards
maks

Use same API as Twig-View for data/attributes

hello

the way to pass datas to the view renderer differs when using PHP-View and Twig-View
I think this is not the right way
maybe PhpRenderer should also implement \ArrayAccess like Twig class does

regards

it support javascrit ejecutions?

I have tried to execute functions such as Ajax and I send the form by ajax, but it does not allow me to finish executing these functions in order to return a processed data from Viwe

Can I access the slim properties from the templates?

Through my API I already have a route that receives parameters and creates users, this works perfectly for me. However, I would like to implement an HTML form for the API to provide API clients, the form must be able to access the method (to the database model), so that from the same form you can create the users.

I already have the simple form in HTML.

The question is: How do I access the slim models, so that, once the values of the form have been obtained, I will be able to send them to the model to create the user.

My route to create users:

$this->post('crear', function ($req, $res, $args) {
        $r = CandidatoCrearValidation::validate($req->getParsedBody());
        if(!$r->response){
          return $res->withHeader('Content-type', 'application/json')
                    ->withStatus(422)
                    ->write(json_encode($r->setResponse(false,"Campos no validados.")));
        }else{
          return $res->withHeader('Content-type', 'application/json')
                  ->write(
                    json_encode($this->model->candidato->crear($req->getParsedBody()))
                  ); 
        }
      });

My route to show the form:

$this->get('crear/form', function ($req, $res, $args) {
      return $this->renderer->render($res, "form-signin.php", $args);
    });

Ambos están dentro del mismo grupo de la ruta de slim.
image

How can I include CSS in templates when using a layout

How can I include template-specific stylesheet files individually when using a layout (without webpack or asset() function)?

I have a layout that I include $this->phpRenderer->setLayout('layout.html.php'); in which I link the default css files for the general page structure etc. that should be loaded on every request.
Now there are some styles that are specific to one and only one template. I don't want to include this CSS file every time. Only when this template is rendered. (For e.g. when error-page is rendered, I want that error.css is loaded, otherwise error.css should not be linked)
Each template content is added via the $content var in the layout inside the <body>-tag and to properly add a CSS file I would need to access the <head> segment.

In Twig, we can use the handy block css

{% block stylesheets %}
    {{ parent() }}

    <link href="{{ asset('user/user.css') }}" rel="stylesheet">
{% endblock %}

How would I do this with PHP-View?
Maybe use Javascript to modify the head after the page loaded? Or include the content in a <style></style> tag?

New Release?

Any idea on a timeline for a new release so I can use the templateExists function added in #77?

Маленькие улучшения

  1. Заменить $this->templatePath на $this->getTemplatePath() и т.п.
    Например:
public function __construct($templatePath, array $attributes = [])
{
    $this->setTemplatePath($templatePath);
    $this->setAttributes($attributes);
}

Это очень важно для наследования класса, сейчас это бессмысленно, все равно все переписывать

  1. Убрать \InvalidArgumentException("Duplicate template key found") в методе fetch(). Исправить protectedIncludeScope:
protected function protectedIncludeScope ($template, array $data) {
    extract($data, EXTR_SKIP); // fix
    include $template;
}
  1. Унаследовать PhpRenderer от \ArrayObject и выкинуть эти функции с $attributes

Error trying to load javascript scripts by it src attribute

Hi!, I am new in PHP and I try to create an API with Slim, but when I try to load a javascript file inside a template by <script src="dist/file.js"></script>, it launch an error: Uncaught SyntaxError: Unexpected token <.

My project structure is:

slim-tutorial
├── public
|    ├── dist
|    |    └── learning.js
|    └── index.php
├── templates
|    └── index.html
├── vendor
├── composer.json
└── composer.lock

My public/index.php code is:

<?php
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;

require __DIR__.'/../vendor/autoload.php';

$app = new \Slim\App(['settings' => $config]);

$container = $app->getContainer();

$container['view'] = new \Slim\Views\PhpRenderer(__DIR__.'/../templates/');

$app->get('/', function(Request $request, Response $response) {
    $response = $this->view->render($response, 'index.html');
    return $response;
});

$app->run();
?>

My templates/index.html code is:

<!DOCTYPE html>
<html lang="es">
    <head>
        <base href="./">
        <meta charset="UTF-8">
        <title>Document</title>
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
    </head>
    <body>
        <section id="root"></section>
        <script type="text/javascript" src="dist/learning.js"></script>
        <!-- <script type="text/javascript">
            // With this code it works, but if I use src attribute in script tag, it crash
            (function() {
                var root = document.getElementById('root');
                var chidlInput = document.createElement('input');
                chidlInput.setAttribute('id', 'name');
                root.appendChild(chidlInput);
            })();
        </script> -->
    </body>
</html>

And my public/dist/learning.js code is:

(function() {
    var root = document.getElementById('root');
    var chidlInput = document.createElement('input');
    chidlInput.setAttribute('id', 'name');
    root.appendChild(chidlInput);
})();

My templates/index.html and public/dist/learning.js codes is to test, in my final project I will use Angular 5 to develop front-end.

I checked this tutorial about Angular and Slim but it not works.

I tried with PHP 5.6 and PHP 7.2 but both launch the same error.

To run this project I use php -S localhost:8080 -t public public/index.php, because I have installed PHP without Apache or Nginx.

I work on Windows 10.

Regards.

You should probably create a proper namespace

Your namespace is currently renderer but it should probably be a namespace that you will release other packages in later on like your own name probably Geggleto.

Also I noticed the code is not PSR2, you might want to change that.

Layout support

Hi,
Is it possible to add layout support ?
Could be very usefull.
Thanks

Ability to specify data outside the render() function

It would be very helpful to have a variable from request ($request->getUri()->getBasePath();) always available on the templates e.g. with a middleware without having to pass the above as parameter to renderer->render on all routes each time (like so):

$app->get(...
   ...
  $args['basepath']=$request->getUri()->getBasePath();
  return $this->renderer->render($response, 'test.php', $args);
});

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.