Is there a way to create a variable inside an object from a string? for example:
class Whatever {
public function createVariables() {
$this["variable_name"] = 100;
}
}
I want to dynamically create variables depending on a set of rules that are defined by a child class.
class Whatever {
public function createAttributes($attribute_name, $attribute_value = NULL) {
$this->{$attribute_name} = $attribute_value;
}
}
This is the way in PHP to add attributes dynamically, nothing related specially to laravel
Laravel also has a built-in way https://github.com/illuminate/database/blob/v4.2.17/Eloquent/Model.php#L2551
class Whatever {
protected $id;
public function createAttributes($id) {
$this->id = $id;
return $this;
}
}
You can call this variable as $this->id inside another functions
Related
I am currently refactoring code from a page parser function to OOP.
I am having difficulties including and running code from a file into main function scope:
Object:
class phpFragment {
private $sData;
function render() {
return include $oElement->sData;
}
}
Object container class:
class pageData {
protected $aPhpFragments;
protected $aCssFragments;
public function outputData($sTag) {
switch($sTag) {
case 'php':
foreach($this->aPhpFragments as $oPhpFragment) {
return $oPhpFragment->render();
}
break;
case 'css':
foreach($this->aCssFragments as $oCssFragment) {
echo $oCssFragment->render();
}
break;
}
}
}
Main function:
function parsePage($sLanguageCode) {
$oTranslator = new translator($sLanguageCode);
$aTranslations = $oTranslator->translations('page');
$oBuilderClass = new builder($aTranslations);
//... queries to get data and set pagedata and get the template file
$oPageData = $oPage->getData();
$aTemplateTags = $oTemplate->getTags();
foreach($aTemplateTags as $sTag) {
$oPageData->outputData($sTag);
}
//....
}
Code of include (example):
<?php
$oBuilderClass->build_element(.... parameters here);
?>
I want to initiate the builder-class only once, because it contains quite some data and I don't want to recreate that on every include.
How can I return the code of the include into the parsePage function where the builderClass can be used?
You can create a Context class that will be a container of your scope variables and helps you include (execute) code inside a context. It will be a singleton class (only one instance will be created).
Here is how to use it: The method current() returns the current instance then you can export variables to the context by using the export() method, it takes a key/value array. The method execute() takes a file name as a parameter and includes it with the exported variables available, you can add temporary variables as a second parameter:
//Somewhere before execute();
oContext::current()->export([
'variable1' => 'value1',
'instance' => $instance
]);
//Then anywhere in your file:
oContext::current()->execute("toBeIncluded.php", [
'tmp_variable' => 'tmp_value'
]);
//toBeIncluded.php
echo $variable1;
echo $instance->method1();
echo $tmp_variable;
In your case:
Main function:
function parsePage($sLanguageCode) {
$oTranslator = new translator($sLanguageCode);
$aTranslations = $oTranslator->translations('page');
$oBuilderClass = new builder($aTranslations);
//export variables to your context
//Don't be aware of memroy usage objects are passed by reference
oContext::current()->export(compact('oBuilderClass'));
//... queries to get data and set pagedata and get the template file
$oPageData = $oPage->getData();
$aTemplateTags = $oTemplate->getTags();
foreach($aTemplateTags as $sTag) {
$oPageData->outputData($sTag);
}
//....
}
Object:
class phpFragment {
private $sData;
function render() {
oContext::current()->execute($oElement->sData);
}
}
You find bellow the class declaration:
oContext.class.php
/**
* Class oContext
*/
class oContext {
/**
* The singleton instance
* #var oContext
*/
private static $instance = null;
/**
* the exported variables
* #var array
*/
private $variables = [];
/**
* Return the singleton or create one if does not exist
*
* #return oContext
*/
public static function current() {
if (!self::$instance) {
self::$instance = new self;
}
return self::$instance;
}
/**
* Export an array of key/value variables
*
* #param $variables
* #return $this
*/
public function export($variables) {
foreach ($variables as $key => $value) {
$this->variables[$key] = $value;
}
return $this;
}
/**
* Include and execute a file in this context
*
* #param $file
* #param array $variables temporary exports will not be added to the context (not available in the next call)
* #return $this
*/
public function execute($file, $variables = []) {
//Populate variables
foreach (array_merge($this->variables, $variables) as $key => $value) {
${$key} = $value;
}
include $file;
return $this;
}
}
I hope this help you achieve your aim.
Good Luck.
If I correctly understand your problem then you want to execute a whole code from php file as a method called from object. If yes then you probably want to use a eval function described here.
With eval function you can read your php file as a string and evaluate it as php code instead of including it.
If your php file use a return statement then following by documentation
eval() returns NULL unless return is called in the evaluated code, in
which case the value passed to return is returned.
you can simply return that value from your method.
If your included files are as simple as you show in example then to achieve this effect you need to replace this part of your code
class phpFragment {
private $sData;
function render() {
return include $oElement->sData;
}
}
with this
class phpFragment {
private $sData;
function render() {
//read a file into variable as string
$phpCode = file_get_contents($oElement->sData);
//prepare code by adding return statement and '?>' at the begining (because you have an open tag in php files).
$phpCode = '?> ' . str_replace('$oBuilderClass->build_element', 'return $oBuilderClass->build_element', $phpCode);
//I guess that included files don't use any variables declared out of file so we need to simply escape every '$' character in file
//that they can evaluate correctly.
$phpCode = str_replace('$', '\$', $phpCode);
return eval($phpCode);
}
}
Sounds like a dependency injection problem: you want $oBuilderClass to be in scope inside the include code?
If you have access to an application dependency container, I'd register the object with that container. In generic terms, something like \Application::bind('Builder', $oBuilderClass), then later do Builder::build_element. However, that you are writing your own view renderer suggests you don't have access to a framework facility with a formal IoC container.
Supposing you don't have an IoC container, the most expedient way would be to do:
$GLOBALS['oBuilderClass'] = new builder(...);
then later in your include:
global $oBuilderClass;
$oBuilderClass->build_element(...);
This is not particularly elegant, however. You might consider passing the builder around, so that at the bottom of the call well you have:
function render(builder $oBuilderClass) {
return include $oElement->sData;
}
which puts $oBuilderClass in scope at the time of the include. I would prefer a formal IoC container first, then passing the object around, then finally if none of these work for you, then using the global variable.
Can anyone tell me the advantage of using the classmap option within PHP Soapclient? Maybe with some practical examples?
The classmap option can be used to map some WSDL types to PHP classes.
Example,
class MyLoginResult {
protected $serverUrl;
protected $sessionId;
public function getServerUrl()
{
return $this->serverUrl;
}
public function getSessionId()
{
return $this->sessionId;
}
public function getServerInstance()
{
$match = preg_match(
'/https:\/\/(?<instance>[^-]+)\.example\.com/',
$this->serverUrl,
$matches
);
return $matches['instance'];
}
}
$client = new SoapClient("books.wsdl",
array('classmap' => array('LoginResult' => "MyLoginResult")));
$loginResult = $client->getLoginResult();
$instance = $loginResult->getServerInstance();
As addition to the comment by hoangthienan, I would show one more advantage when using a mapped class.
E.g. you could extend the class by a __set() method, that would be triggered when the SoapClient passes its data to the mapped class (you should know, the method will not be triggered if your property is public).
In that case you can alternate the data passed from SoapClient before you assign it to your Data-Class.
class MyLoginResult {
protected $serverUrl;
protected $sessionId;
private $is_logged_in;
public function __set($name, $value) {
if ($name == 'login_status') {
$this->is_logged_in = ($value == 'logged_in') ? true : false;
} else {
$this->$name = $value;
}
}
public function loginSuccessfull() {
return $this->is_logged_in;
}
// class code from hoangthienan
}
e.g. in this example we get a string from Soap, but we store a bool-value in our class.
You could use this for other changes to e.g. if you like to store your internal variables in a array instead of using direct properties.
This declares properties on the fly:
class metadata {
function __construct($file) {
/* Argument: Array containing data of a single file */
while ($pointer = key($file)) {
$this->$pointer = current($file);
next($file);
}
}
}
I want all the properties that are declared in the while loop as $this->$pointer to be private.
How do I achieve that, without setting a long private $prop1, $prop2, $etc;?
The main purpose is, to keep the code short. The class I am writing probable takes 20 private properties and I was just wondering if I can save the typing.
You can use stdclass. Here is official link, http://us1.php.net//manual/en/reserved.classes.php
Like,
class metadata {
function __construct($file) {
private $this->file_meta = new stdClass();
while ($pointer = key($file)) {
$this->file_meta->pointer = current($file);
next($file);
}
$this->file_meta->other_var = 'some value';
}
}
I am getting this error and i can't see what i am doing wrong. I have done the same thing with other objects from other classes which are built in the exact same way and i can't see why i am getting this error now.
The code in which i create the object is this one:
$consulta2 = "SELECT * FROM TiposDireccion WHERE Cliente_CIF='$cif' and Direccion_Direccion='$direccion' and Direccion_CP=$cp ";
echo($consulta2."</br>");
if ($resultado2 = $conexion->query($consulta2)){
while($fila2 = $resultado2->fetch_object()){
$tipodireccion78=$fila2->TipoDireccion_Tipo;
//we see here that the select is returning a correct string with a correct value
echo($tipodireccion78);
//we try to instantiate and it fails =(
$unTipoDireccion=TipoDireccion::constructor1($tipodireccion78);
This is the class TipoDireccion:
<?php
class TipoDireccion{
private $tipo;
private $descripcion;
//Construct auxiliar
function __construct() {
}
//Constructor 1 : completo
function constructor1($tipo) {
$tipoDireccion = new TipoDireccion();
$tipoDireccion->tipo = $tipo;
return $tipoDireccion;
}
function ponTipo($tipo) {
$this->tipo = $tipo;
}
function devuelveTipo() {
return $this->tipo;
}
function ponDescripcion($descripcion) {
$this->descripcion = $descripcion;
}
function devuelveDescripcion() {
return $this->descripcion;
}
}
?>
Thank you a lot in advance!
Don't know if this is still relevant to you, but in case anyone else comes on here for an answer. The problem is in this function:
function constructor1($tipo) {
$tipoDireccion = new TipoDireccion();
$tipoDireccion->tipo = $tipo;
return $tipoDireccion;
}
Because in the class definition, you define private $tipo; and then you try and assign $tipoDireccion->tipo to what was passed through the function. However, you aren't trying to access that variable through the scope of the class, you are trying to assign it from the 'public' scope as far as the class is concerned.
The fix for this has two options, the first one would be to change private $tipo; to public $tipo;. But that isn't a good solution as you have an assignment function for it.
Instead, use your functions that you made, which would make the function look like:
function constructor1($tipo) {
$tipoDireccion = new TipoDireccion();
$tipoDireccion->ponTipo($tipo);
return $tipoDireccion;
}
That's how you need to access it from the public scope, which you are doing after you initiate a new one.
function constructor1($tipo) {}
should be
static function constructor1($tipo) {}
I'm trying to whip up a skeleton View system in PHP, but I can't figure out how to get embedded views to receive their parent's variables. For example:
View Class
class View
{
private $_vars=array();
private $_file;
public function __construct($file)
{
$this->_file='views/'.$file.'.php';
}
public function set($var, $value=null)
{
if (is_array($var))
{
$this->_vars=array_merge($var, $this->_vars);
}
else
$this->_vars[$var]=$value;
return $this;
}
public function output()
{
if (count($this->_vars))
extract($this->_vars, EXTR_REFS);
require($this->_file);
exit;
}
public static function factory($file)
{
return new self($file);
}
}
test.php (top level view)
<html>
<body>
Hey <?=$name?>! This is <?=$adj?>!
<?=View::factory('embed')->output()?>
</body>
</html>
embed.php (embedded in test.php
<html>
<body>
Hey <?=$name?>! This is an embedded view file!!
</body>
</html>
Code:
$vars=array(
'name' => 'ryan',
'adj' => 'cool'
);
View::factory('test')->set($vars)->output();
Output:
Hey ryan! This is cool! Hey [error for $name not being defined]
this is an embedded view file!!
The problem is the variables I set in the top level view do not get passed to the embedded view. How could I make that happen?
So, I'm not exactly answering your question, but here's my super-simple hand-grown template system. It supports what you're trying to do, although the interface is different.
// Usage
$main = new SimpleTemplate("templating/html.php");
$main->extract($someObject);
$main->extract($someArray);
$main->name = "my name";
$subTemplate = new SimpleTemplate("templating/another.php");
$subTemplate->parent($main);
$main->placeholderForAnotherTemplate = $subTemplate->run();
echo $main; // or $main->run();
// html.php
<html><body><h1>Title <?= $name ?></h1><p><?= $placeHolderForAnotherTemplate ?></p></body></html>
<?php
// SimpleTemplate.php
function object_to_array($object)
{
$array = array();
foreach($object as $property => $value)
{
$array[$property] = $value;
}
return $array;
}
class SimpleTemplate
{
public $source;
public $path;
public $result;
public $parent;
public function SimpleTemplate($path=false, $source=false)
{
$this->source = array();
$this->extract($source);
$this->path($path);
}
public function __toString()
{
return $this->run();
}
public function extract($source)
{
if ($source)
{
foreach ($source as $property => $value)
{
$this->source[$property] = $value;
}
}
}
public function parent($parent)
{
$this->parent = $parent;
}
public function path($path)
{
$this->path = $path;
}
public function __set($name, $value)
{
$this->source[$name] = $value;
}
public function __get($name)
{
return isset($this->source[$name]) ? $this->source[$name] : "";
}
public function mergeSource()
{
if (isset($this->parent))
return array_merge($this->parent->mergeSource(), $this->source);
else
return $this->source;
}
public function run()
{
ob_start();
extract ($this->mergeSource());
include $this->path;
$this->result = ob_get_contents();
ob_end_clean();
return $this->result;
}
}
well, you create a new instance of the class, so there are no variables defined in the embedded template. you should try to copy the object, rather than creating a new one.
edit: I'm talking about the factory method
The main issue is that your views have no direct knowledge of each other. By calling this:
<?=View::factory('embed')->output()?>
in your "parent" view, you create and output a template that has no knowledge of the fact that it is inside another template.
There are two approaches I could recommend here.
#1 - Associate your templates.
By making your embedded templates "children" of a parent template, you could allow them to have access to the parent's variables at output() time. I utilize this approach in a View system I built. It goes something like this:
$pView = new View_Parent_Class();
$cView = new View_Child_Class();
$pView->addView($cView);
At $pview->render() time, the child view is easily given access to the parent's variables.
This method might require a lot of refactoring for you, so I'll leave out the dirty details, and go into the second approach.
#2 - Pass the parent variables
This would probably be the easiest method to implement given the approach you've taken so far. Add an optional parameter to your output method, and rewrite it slightly, like this:
public function output($extra_vars = null)
{
if (count($this->_vars))
extract($this->_vars, EXTR_REFS);
if (is_array($extra_vars)) extract($extra_vars, EXTR_REFS);
require($this->_file);
exit;
}
If you add a simple getter method as well:
public function get_vars()
{
return $this->_vars;
}
Then you can embed your files with what is effectively read-access to the parent's variables:
<?=View::factory('embed')->output($this->get_vars())?>
$this will be a reference to the current template, ie. the parent. Note that you can have variable name collisions via this method because of the two extract calls.
You could make your $_vars property static, not particularly elegant, but would work for what you are trying to achieve.
On a side note... your array_merge() in the set() function is wrong, swap your 2 variables around.