Trouble setting up closures for class methods - php

I am architecting out a class and so far have something roughly like the following
in main.php
$coolObject = new CoolObject(true);
foreach($array as $key=>$val)
{
$coolObject->doFunction($val);
}
then inside of CoolObject:
class CoolObject
{
private $_useFunction;
public function __construct($useFoo)
{
//THIS IS WHERE I NEED HELP
if($useFoo){
$this->_useFunction = $this->_foo();
}
else{
$this->_useFunction = $this->_bar();
}
}
public function doFunction($values)
{
$this->_useFunction($values);
}
private function foo($values){
//do stuff
}
private function bar($values){
//do other stuff
}
}
I realize I could put an if statement or switch into doFunction that could handle this functionality, but since I'm not hip on closures/lamdas I wasn't sure if this is the right situation for them or if I'm just totally off base

What you're trying to achieve (as far as I know) are function pointers in PHP.
You should really take a look in: Array Pointer to a function in PHP
I still think inheritance will be the easiest/cleaniest way to solve this.
So you would probably end with:
class CoolObject
{
private $_useFunction;
public function __construct($useFoo)
{
//THIS IS WHERE I NEED HELP
if($useFoo){
$this->_useFunction = 'foo';
}
else{
$this->_useFunction = 'bar';
}
}
public function doFunction($values)
{
//Edited as #newacct corrects
$this->$_useFunction($values);
}
private function foo($values){
//do stuff
}
private function bar($values){
//do other stuff
}
}

Related

PHP Classes function inside function?

I'm self-studying the PHP language. And I'm focused on the latest PHP OOP language.
I search for some "ready-to-install" PHP software and as I scan for some references to search and know, I saw lines of code with a structure like this (can't remember so I'll create my own):
$myapp->settings->getValue('openforum');
$myapp->settings->setValue('closeformaintenance', '1');
So my question is, how can I reproduce the code above? I don't know what term to use that line of code (objects, I guess?).
Something like this:
$newLogin->search($uid)->setLogin($dateToday);
Like that. I really need to do that way so I can organize my coding structure. Thanks by the way.
And also for the final question, IS THAT POSSIBLE?
Here's a fairly straight forward way of looking at it, using dependency injection.
Try it out: https://3v4l.org/iSJgL
Note, the below requires PHP 7 due to the string type hint. Remove that and I believe it should work in 5.6 just fine.
<?php
$myapp = new MyApp(new SettingsBag([
'works' => false,
'random' => rand(),
]));
var_dump($myapp->settings()->get('random'));
var_dump($myapp->settings()->get('works'));
// Let's change it up...
$myapp->settings()->set('works', true);
// Now it should be true.
var_dump($myapp->settings()->get('works'));
These would normally have namespaces like \App and/or \App\Configuration, but I ignore that here so it's easier to follow:
class MyApp {
private $settings_bag = null;
function __construct(SettingsBag $settings_bag)
{
$this->settings_bag = $settings_bag;
}
public function settings()
{
return $this->settings_bag;
}
}
class SettingsBag {
private $settings = null;
function __construct(array $settings = [])
{
$this->settings = $settings;
}
public function set(string $key, $value)
{
return $this->settings[$key] = $value;
}
public function get(string $key)
{
return $this->settings[$key];
}
}
What you try to achieve is called method chaining. You can get this by the following:
<?php
class TestClass {
private $val = '';
public function test1($val) {
$this->val = $val;
return $this;
}
public function test2() {
echo 'Hello '.$this->val;
}
}
$test->test1('World')->test2(); // Hello World
You have simply to return the instance of the object on the method to allow the method chaining.
You can read more here.
It's method chaining.
See code below:
class T {
public function test() {
// do something
return $this;
}
}
$x = new T;
$x->test()->test();

Assign functions from another file to a Class

I am trying to add functions to class from a separate file, I wonder if this could be possible!
$mClass = new MyClass();
$mClass->new_Functions[0](10); // Is there a way to have it in this form?
class myClass
{
private $Pvar = 5;
$new_Fcuntions;
function __construct()
{
include('additional.functions.php');
$arr = get_defined_functions();
$this->new_Functions = $arr['user'];
// trying to call the function with parameter 10
call_user_func(array($this, $this->new_Functions[0]), 10);
}
}
[additional.functions.php] file
function operate($y)
{
return $this->Pvar * $y;
}
----- Edited ------- as it wasn't clear!
"additional.functions.php" is a module and there will be multiple modules to be added to the application, and every module could have more than single function and modules could call one another!
additional.functions.php [module file]
function operate($y)
{
return $this->Pvar * $y;
}
function do-more($foo)
{
return $this->operate(20) + $foo;
}
another.functions.php [another module]
function do-another($foo)
{
return $this->do-more(30) - $foo;
}
function add($foo, $bar)
{
return $foo + $bar;
}
appreciate every participation, its been a while since I am trying to maneuver around with it!
Is this possible or should I give up!
It looks to me like you are looking for Traits, which are a new feature as of PHP 5.4.0. Using traits, you can have snippets of code "mixed in" to other classes, a concept known as "horizontal reuse".
If you are not looking for traits, it's possible that you could do what you wanted with Runkit, however I would suggest staying as far away from it as possible, if you are not genuinely interested in PHP internals as well.
In any event, whatever you are trying to do is very interesting
I got it to work with dependency injection. The pvar has to be public or create a __get method to return the private variable. I also used the function name because it seems cleaner to me to use it via name rather than it's position in the list but if you want to keep that then just put $key where you see $value from the line: $this->function_list[$value] = ...
function operate($y, $that)
{
return $that->Pvar * $y;
}
class Example {
public $function_list = array();
private $Pvar = 5;
public function __construct()
{
$list = get_defined_functions();
$that = $this;
foreach ($list['user'] as $key => $value) {
$this->function_list[$value] = function() use ($value, $that) {
print call_user_func_array($value, array_merge(func_get_args(), array($that )));
};
}
}
public function __get($key)
{
if (isSet($this->$key)) {
return $this->$key;
} else {
throw new \Exception('Key "'.$key.'" does not exist');
}
}
}
$Ex = new Example();
$Ex->function_list['operate'](10);
If you want to extend MyClass from your modules (and not to initialize it, like in your example code), than you could do it in a way like this:
<?php
namespace modules\MyModuleA;
class MyClassExtension
{
private $MyObject;
public function __construct(\MyClass $MyObject)
{
$this->MyObject = $MyObject;
}
public function doSomething($anyParameter)
{
return $this->MyObject->doSomethingElse($anyParameter * 5, 42, 'foo');
}
}
And MyClass:
<?php
class MyClass extends \Extensible
{
// some code
}
abstract class Extensible
{
private $extensions = [];
public function extend($extension)
{
$this->extensions[] = $extension;
}
public function __call($methodName, $parameters)
{
foreach ($this->extensions as $Extension) {
if (in_array($methodName, get_class_methods($Extension))
return call_user_func_array([$Extension, $methodName], $parameters);
}
throw new \Exception('Call to undefined method ' . $methodName . '...');
}
public function hasExtension($extensionName)
{
return in_array($this->extensions, $extensionName);
}
}
And put it all together:
<?php
$moduleNames = ['MyModuleA', 'MyModuleB'];
$MyObject = new \MyClass;
foreach ($moduleNames as $moduleName) {
$className = '\\modules\\' . $moduleName . '\\MyClassExtension';
$module = new $className($MyObject);
$MyObject->extend($module);
}
// Now you can call a method, that has been added by MyModuleA:
$MyObject->doSomething(10);
You should add an interface for the extension classes of course...
The problem is: What happens if any code in your application calls a method of $MyObject, that is not there, because the module has not been loaded. You would always have to check if ($MyObject->hasExtension('ModuleA')) { ... }, but, of course, the application shouldn't be aware of any module. So I would not design an application in such a way.
I would suggest to use traits (mix-ins). See PHP reference
If you can have another class in that file instead of file with functions
- the best solution will be Traits
http://php.net/manual/en/language.oop5.traits.php
or using inheritance
If you move that code to class you can avoid a lot of unnecessary code. I mean:
include('additional.functions.php');
$arr = get_defined_functions();
$this->new_Functions = $arr['user'];
// trying to call the function with parameter 10
call_user_func(array($this, $this->new_Functions[0]), 10);
It'll be e.g.:
class myClass extends MyBaseClassWithMyAwesomeFunctions
{
private $Pvar = 5;
}
Maybe this approach helps you:
In the files with the additional functions, don't define named functions, but return a closure, that expects (at least) the object (instance of MyClass) as parameter:
<?php
// additional.functions.php
return function ($myObject) {
$Object->multiplyPvar($myObject->getTheNumber());
$Object->doSomethingElse(42, 'foo');
};
The client, that builds MyClass collects those functions from the files into the array:
<?php
$files = [
'/path/to/my/additional.functions1.php',
'/path/to/my/additional.functions2.php'
];
$initFunctions = [];
foreach ($files as $path)
$initFunctions[] = include $path;
$MyObject = new \MyClass($initFunctions);
The constructor then calls those functions:
<?php
class MyClass
{
public function __construct(array $additionalInitFunctions)
{
foreach ($additionalInitFunctions as $additionalInitFunction)
$additionalInitializerFunction($this); // you can also add parameters of course
}
}
This way the class keeps very well testable as well as the function files. Maybe this could help you in any way. You should never ever think about modifying the internal (private) state of an object directly from any code from outside of the class. This is not testable! Think about writing tests before you implement your code (called "test driven development"). You will see, it is not possible to test a class, if you allow any code outside of that class to modify the internal (private) state of the class instance. And you don't want to have this. If you change some internal implementation detail in your class without breaking the unit test of that class, you will anyways probably break some code in any of your additional.functions.php files and no test will tell you: "Hey: you've broken something right now".

PHP/Xdebug Nesting level error

Yes I looked at the previous questions in this topic and I still was not able to solve the problem. I tried ini_set('xdebug.max_nesting_level', 5000); in my script and still Im getting the error. So I will show you what I'm doing to hopefully get some and and get to a resolution.
Basically I running a test with data from my database.
include 'testSettings.php';
//get config
$conf = Config::getInstance('sendCsv.php');
$conf->getConfig();
$formatter = new FormatterContext(new CSVFormatter($conf));
$formatter->formatLoads(null);
The complain comes when I call `$formatter->formatLoads(null);
So here is FormatterContext()
class FormatterContext
{
private $strategy;
public function __construct(IFormatter $formater)
{
$this->strategy = $formater;
}
public function formatLoads()
{
return $this->formatLoads();
}
}
The interface:
abstract class IFormatter
{
private $config;
private $formatted;
private $fileMaps;
private $fileRows;
abstract public function formatLoads($loads);
public function __construct(Config $conf)
{
$this->fileMaps = $conf->__get('fileMaps');
$this->fileRows = $conf->__get('fileRows');
}
}
The Strategy:
class CSVFormatter extends IFormatter
{
public function formatLoads($loads)
{
echo "hello world!\n";
}
}
Now I really don't know what am I doing wrong here. This is the first time I have encounter this error. Beyond nesting level = 5000 I have not tried, I think at that point something is really wrong. Thanks
Arg! Silly me... In the context class I'm calling the context formatLoad() instead of the strategy. Ooops!
The context class should be like:
class FormatterContext
{
private $strategy;
public function __construct(IFormatter $formater)
{
$this->strategy = $formater;
}
public function formatLoads()
{
// return $this->formatLoads();
return $this->strategy->formatLoads();
}
}

Attempt to assign property of non-object error

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) {}

Templating system scoping issue

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.

Categories