I'm currently creating a framework, just for the sake of a personal study-project, i.e. to be able to evaluate php-frameworks in general futurewise. As I'm starting to build up more appreciation for some of the frameworks, especially about the way they designed their modules and made them available in 'their scope', during the development of this project I'm also baffled as to why almost all framework-developers include the (view)templates inside the scope of their classes.
Perhaps there are some developers roaming these boards that are able to be constructive about it.
An example (which for the sake of clarity is very much oversimplified).
Why do this...
<?php
class TemplateInc {
public $template;
public function __construct($page) {
$this->template = $page;
}
public function render() {
require_once $this->template;
}
}
$template_include = new TemplateInc('mypage.html');
$template_include->render();
?>
...when its also possible to do it like this, at the same time being able to also use the global-scope variables in the templates (if appropriate) ?
<?php
class TemplateName {
public $template;
public function __construct($page) {
$this->template = $page;
}
public function getTemplate() {
return $this->template;
}
}
$templatename = new TemplateName('mypage.html');
require_once $templatename->getTemplate();
?>
Usually the template class would also make additional values available to the template. E.g.:
public function render(array $vars) {
extract($vars);
require $this->template;
}
Or the template could make use of $this->.. to access other methods/objects/values/services which are part of the class. None of this is possible if you do the require outside of the class. Then only global variables are available to the template, of which you hopefully don't have any (don't use global). Doing all this inside a method allows you do to something like extract($vars) without cluttering the global scope.
Further, instead of a nicely self-contained call to $template->render(), you now need to do require $template->render(), which kind of negates the point of writing a class in the first place; the caller is still doing all the work and the class itself basically doesn't (can't) do anything.
Related
I'm trying to encapsulate an included file within a function. Nothing from the included file should be accessible outside the encapsulation function.
What I've found is that variables from the included file will be limited to the scope of the encapsulation function, but functions from the included file will be globally accessible.
index.php:
function encapsulate_this() {
include 'some_file.php';
}
encapsulate_this();
echo say_hello();
echo $test_variable;
some_file.php:
$test_variable = "Hi!";
function say_hello() {
return "Hello!";
}
Output:
Hello!
In the above example echoing say_hello() will work, but $test_variable will not.
How can I successfully encapsulate functions from the included file so they are not globally accessible?
All functions (and classes) are global in PHP, there's no such concept as "scope" for those things. I only see two options:
Name your functions and/or classes in a way so they won't conflict with other global things, namespaces can help a lot here. E.g. if you rigorously apply the PSR-4 naming standards to all files and all functions, it doesn't really matter whether functions are exposed globally or not, since their names cannot conflict.
Use anonymous functions, which is the only way functions can be scoped:
$say_hello = function () {
return 'Hello';
};
Obviously, both depend on the included file to cooperate; if you're including a third party file which does not adhere to either standard, you're SOL.
You should consider anonymous classes (php7+). In a class you can have public, protected and private members.
public members are accessible from outside the class.
protected members are accessible from inside the class and classes which inherit the class.
private members are only accessible from inside the class.
A little example:
some_file.php
$helloClass = new class('HelloWorld') {
public function say_hello_public(){
return "Hello!";
}
private function say_hello_private(){
return "Hello!";
}
};
index.php
function encapsulate_this() {
include 'some_file.php';
echo $helloClass->say_hello_public(); //Works because it's a public method.
echo $helloClass->say_hello_private(); //Won't work because it's a private method.
}
encapsulate_this();
echo $helloClass->say_hello_public(); //Won't work outside scope
I've been attempting to create a PHP loader class that'll take care of all of my directory issues. I've mostly gotten it to work, but it breaks when including global functions.
Here's what I'm using:
<?php
class Loader {
public function __construct() { ... }
private function check_if_file_exists($file) { ... }
public function load($file) {
$this->check_if_file_exists($file); //Throws fatal Exception if not a file
//The "important" stuff: works with global vars, not with global functions:
extract($GLOBALS, EXTR_REFS);
ob_start();
require_once "{$this->path}/$file";
return ob_get_clean();
}
}
This lets me do the following:
<?php
$loader = new Loader();
$loader->load('file.php'); //Class takes care of path stuff--don't worry about it
//This works:
print $variable_in_file_dot_php;
//This does NOT work:
function_in_file_dot_php();
How can I make it so that function_in_file_dot_php(); works?
Better you use AutoLoader class already available in php.Refer this url http://php.net/manual/en/language.oop5.autoload.php
i'm going to try to answer your question as a technical curiousity, but i strongly advise you not to do this.
Referring to the include/require documentation I see that variables defined inside included files will inherit the variable scope of the line that called require. In your case this will be the variable scope of Loader::load() method inside some instance of Loader class
Therefore $variable_in_file will not be available globally. unless you
define the $var before calling the include statement, thus giving it global scope.
declare it global inside your calling method (Loader::load())
You acomplish #2 with extract($GLOBALS...) however in order to do #1, you must have a priori knowledge of what is being included before you include it... invalidating your attempt at generalization with the Loader class.
function_in_file() however should be available in the global scope, I'd like to see your file.php and error message. Here's mine.
$cat foo.php
public function load($file) {
extract($GLOBALS,EXTR_REFS);
require_once $file;
}
}
$variable = 1;
$loader = new Loader();
$loader->load('file.php');
echo "\n" . $variable;
echo "\n" . method();
$cat file.php
<?php
function method() {
echo "hi i am a method";
}
outputs
$php foo.php
hello i am a variablehi i am a method
but seriously, don't do this. You seem to be trying to use includes() as a vector of code reuse, when it is mostly envisioned as a method for code separation. You are messing with phps' natural scoping in a hard to debug and unpredictable way. This is an anti-pattern.
I have been pouring over Zend_View and the various classes and interfaces that make up the view. One thing I am attempting to replicate in a project that does not use zend in any way shape or form is the:
$this->view->variable = 'Hello world';
that you can set in a controller and then do:
echo $this->view->variable;
My ultimate goal is to do something like:
$this->variable = new SomeClass
and then else where, in a view specifically, do:
$this->variable->someMethod();
My question is:
How would I replicate what zend does to do something simmilar with out using global variables?
How is zend able to do something like $this->view with out ever instantiating or saying what view is?
this would help me understand how, variables are passed around or objects are passed from the logic to the view and how php allows for something like $this->view to work when in a view or not.
note: this is not a Zend specific question and "use zend" is not the answer. I am looking to replicate a specific feature. My project does not in any way use or affiliate with zend.
I don't know why exactly you want to achieve this, but as a super simple setup (which is by no means suited to be the basis of an MVC framework) you can look at this:
<?php
class Controller
{
private $view = null;
public function __construct()
{
$this->view = new View();
$this->view->someVar = "foobar";
}
public function render()
{
include "view.php";
}
}
class View
{
}
$controller = new Controller();
$controller->render();
And then, in view.php, you can do:
<?php
echo $this->view->someVar;
Beware: This code only shows HOW it's possible to achieve such a construct, it does not anything useful at all ;).
It's actually pretty simple. When you use include, require, eval et al., the loaded code is brought in to the current scope. So if you have a template file
template.php
<span><?=$this->view->somevar?></span>
Controller.php
<?php
class Controller
{
private $view;
public function doSomething()
{
$this->view->somevar = 'Hello World';
include 'template.php';
}
}
index.php
<?php
require 'Controller.php';
$oC = new Controller();
$oC->doSomething();
Blamo.., template.php is able to call $this->view->somevar as it is treated as part of Controller.php. Running php index.php on the CLI produces
<span>Hello World</span>
To elaborate a tiny bit, if $this->view inside of Controller.php were a class you've defined rather than a simple instance of stdClass as in the above demonstration, and it had a function someMethod, you could call $this->view->someMethod() from template.php just the same.
I have a php site which flows as shown below. Please note I'm leaving out most of the code (wherever theres an ellipses).
index.php
include template.php
...
$_template = new template;
$_template->load();
...
template.php
class pal_template {
...
public function load() {
...
include example.php;
...
}
example.php
...
global $_template;
$_tempalate->foo();
...
Now, this works fine. However, I end up having a ton of files that are displayed via the $_template->load() method, and within each of these files I'd like to be able to make use of other methods within the template class.
I can call global $_template in every file and then it all works fine, but if possible I'd really like for the object $_template to be available without having to remember to declare it as global.
Can this be done, and what is the best method for doing it?
My goal is to make these files that are loaded through the template class very simple and easy to use, as they may need to be tweaked by folks who basically know nothing about PHP and who would probably forget to put global $_template before trying to use any of the $_template methods. If $_template were already available in example.php my life would be a lot easier.
Thanks!
You can define globals before you include 'example.php'.
global $_template;
include 'example.php'
Or you could do this:
$_template = $this;
include 'example.php'
Or inside example.php:
$this->foo();
The use of global is strongly not recommended.
By the way, consider this :
-> index.php
$_template = new template;
$_template->load();
-> template.php
class template {
public function load() {
include 'example.php';
}
public function showMessage($file) {
echo "Message from '{$file}'";
}
}
-> example.php
<?php
$this->showMessage(__FILE__);
Will output something like
Message from '/path/to/example.php'
I recommend you to not use "global", as Yanick told you as well.
What you probably need is the Registry design pattern. Then you can add the template to the registry and give it to every object. In general I would recommend you to learn about design patterns. Here are some more you could learn.
Current situation:
I have the current version of my MVC Framework which uses classes as controllers.
I have some "vintage" modules from my old MVC Framework which uses simple, flat includes as controllers.
Much simplified that means:
New Version:
<?PHP
class blaController extends baseController {
private $intVar;
function dosomethingFunction() {
$this->intVar = 123;
$this->view('myView');
}
}
?>
Old Version:
<?PHP
$globalVar = 123;
// view "controllername" is automatically shown
?>
I'm now trying to write a wrapper to be able to use my old controllers in my new MVC without having to rewrite everything. To do so, I have a "wrapper" controller:
class wrapController extends baseController {
function dosomethingFunction() {
require 'old_dosomething.function.php';
$this->view('old_dosomething_view');
}
}
(Once again: This is VERY, VERY simplified - just to get the idea over. Not actual code.)
The problem with that approach is, that the previously global variable $globalVar now only exists inside of the method "dosomethingFunction" and cannot be accessed by the view.
This wouldn't be the case if I could force the require to behave as "in global scope" so that $globalVar would once again be available in global scope.
So: Is there some way to achieve "require_global" or something similar?
(One solution for my problem would be to modify my old controllers to start with a bunch of "global" commands, but I'd prefer a solution where I don't have to change so much of that old code.)
(Note: Please don't tell me that GLOBALS are bad. It totally misses the point of this question. Just accept that it is a requirement to keep some old code working in a newer, cleaner environment.)
You can add local variables defined within dosomethingFunction() to global scope:
class wrapController extends baseController {
function dosomethingFunction() {
require 'old_dosomething.function.php';
//begin added code
$vararr = get_defined_vars();
foreach($vararr as $varName => $varValue)
$GLOBALS[$varName] = $varValue;
//end added code
$this->view('old_dosomething_view');
}
}
Note, that for this to work as expected, you should call require before using any other thing in the function. get_defined_vars() returns only variables from the current scope, so no array_diff hacks are needed.
This is the easiest solution I can think of.
Use the get_defined_vars() function twice and get a diff of each call to determine what variables were introduced by the required file.
Example:
$__defined_vars = get_defined_vars();
require('old_dosomething.function.php');
$__newly_defined_vars = array_diff_assoc($__defined_vars, get_defined_vars());
$GLOBALS = array_merge($GLOBALS, $__newly_defined_vars);
$this->view('old_dosomething_view');
Hmmm, this is an issue I've never before seen. I suppose you could do this
class wrapController extends baseController {
function dosomethingFunction() {
require 'old_dosomething.function.php';
// Force "old" globals into global scope
$GLOBALS['globalVar'] = $globalVar;
$this->view('old_dosomething_view');
}
}
But that's a pretty tedious, manual process as well, depending on how many globals we're talking about. I'll think about this, but I don't know of any "auto-magic" solution off the top of my head.
For anybody interested: My (so far) final version:
class wrapController extends baseController {
function dosomethingFunction() {
// ... do some initialisation stuff ...
$__defined_vars = array_keys(get_defined_vars());
require 'old_dosomething.function.php';
$__newly_defined_vars = array_diff(
array_keys(get_defined_vars()),
$__defined_vars,
array('__defined_vars')
);
foreach ($__newly_defined_vars as $var) {
$GLOBALS[$var] = &$$var;
}
$this->view('old_dosomething_view');
}
}
Ugly, but it works. Thanks for all your great help!
Have you tried Zend_Registry from Zend Framework?
The registry is a container for storing objects and values in the application space. By storing the value in the registry, the same object is always available throughout your application. This mechanism is an alternative to using global storage.
http://framework.zend.com/manual/en/zend.registry.html