I'm trying to find a workaround for static variables not being copied over to extending classes (which doesn't play nicely with late static binding), here is what I thought might work, but gives me a "PHP Fatal error: Can't use function return value in write context" :
<?php
class Person
{
protected static $tlsb_names = ['name'];
protected static $tlsb_vars = [];
public static function & __callStatic($method,$args)
{
echo "call static " . $method . " on " . get_called_class() . "\n";
if(in_array($method,static::$tlsb_names))
{
if(!array_key_exists(get_called_class(),static::$tlsb_vars))
{
static::$tlsb_vars[get_called_class()] = [];
}
if(!array_key_exists($method, static::$tlsb_vars[get_called_class()]))
{
echo "set var $method for " . get_called_class() . "\n";
static::$tlsb_vars[get_called_class()] = null;
}
return static::$tlsb_vars[get_called_class()][$method];
}
}
public static function show_name()
{
static::name() . "\n";
}
public static function call_me_al()
{
static::name() = "Al";
}
public static function call_me_joe()
{
static::name() = "Joe";
}
}
class Al extends Person{}
class Joe extends Person{}
Al::call_me_al();
Joe::call_me_joe();
Al::show_name();
Joe::show_name();
The problematic part is with the lines :
public static function call_me_al()
{
static::name() = "Al";
}
Apparently this is a compile-time error since non of my echo's are run.
What am I doing wrong here?
The following line of code is wrong:
public static function & __callStatic($method,$args)
You need to match the definition of that __callStatic functionDocs and that is without return by reference:
public static function __callStatic($name, $arguments)
So what you try to achieve is not possible.
And the other problem you circle around with should be able to solve with late static binding (LSB)Docs.
Also keep in mind that Magic is hard to debug, so get your step-debugger ready and step through the application so you can better understand what is actually happen. The debugger in PHP is called Xdebug, most PHP IDEs and editors support it.
Related
I have this code
class View
{
const DEFAULT_VIEWS_DIRECTORY = $_SERVER["DOCUMENT_ROOT"] . "views/";
}
but, it gives me syntax error
Parse error: Parse error: syntax error, unexpected '$_SERVER' (T_VARIABLE) in C:\xampp\htdocs\classes\View.class.php on line 17
I checked the manual and it says
The value must be a constant expression, not (for example) a variable, a property, or a function call.
is there any work around to do what I want ?, because I consume this value in the class heavily, and having this as a constant will make the class more pretty
As you noticed, Expression is not allowed as class constant value, but it doesn't stop you to initiate your default view directory once and and use it on different occasion:
class View
{
private $defaultViewDirectory;
public function __construct()
{
$this->defaultViewDirectory = $_SERVER["DOCUMENT_ROOT"] . "views/";
}
public function getDefaultViewDirectory()
{
return $this->defaultViewDirectory;
}
}
Or you could implement a Singleton pattern like:
class View
{
private $defaultViewDirectory;
private function initDefaultViewDirectory()
{
$this->defaultViewDirectory = $_SERVER["DOCUMENT_ROOT"] . "views/";
}
public function getDefaultViewDirectory()
{
if (is_null($this->defaultViewDirectory)) {
$this->initDefaultViewDirectory();
}
return $this->defaultViewDirectory;
}
}
Or if you need a static access:
class StaticView
{
private static $defaultViewDirectory;
private static function initDefaultViewDirectory()
{
self::$defaultViewDirectory = $_SERVER["DOCUMENT_ROOT"] . "views/";
}
public static function getDefaultViewDirectory()
{
if (is_null(self::$defaultViewDirectory)) {
self::initDefaultViewDirectory();
}
return self::$defaultViewDirectory;
}
}
So you could call StaticView::getDefaultViewDirectory()
men and women!
My problem is I don't really know what is the best way to design so I defined 2 classes 1'st one is:
class color
{
private $id = NULL;
private $name = '';
private $rgb = NULL;
private $cmy = NULL;
private $wavelength = NULL;
private $frequency = NULL;
public function __construct($name, $rgb, $cmy, $wavelenght, $frequency)
{
setName($name);
setRGB($rgb);
setCMY($cmy);
setWavelength($wavelength);
setFrequency($frequency);
}
public function __destruct()
{
}
public function setName($name)
{
$this->name=$name;
}
public function setRGB($rgb)
{
$this->rgb=$rgb;
}
public function setCMY($cmy)
{
$this->cmy=$cmy;
}
public function setWavelength($wavelength)
{
$this->wavelength=$wavelength;
}
public function setFrequency($frequency)
{
$this->frequency=$frequency;
}
public function getId()
{
return $this->id;
}
public function getName()
{
return $this->name;
}
public function getRGB()
{
return $this->rgb;
}
public function getCMY()
{
return $this->cmy;
}
public function getWavelength()
{
return $this->wavelength;
}
public function getFrequency()
{
return $this->frequency;
}
public function toJSON()
{
return "{'id':'".$this->id."', 'name':'".$this->name."', 'rgb':'".$this->rgb."', 'cmy':'".$this->cmy."', 'wavelength':'".$this->wavelength."', 'frequency':'".$this->frequency."'}";
}
public function toCSV()
{
return $this->id . ", " . $this->name . ", " . $this->rgb . ", " . $this->cmy . ", " . $this->wavelength . ", " . $this->frequency;
}
public function toHTML()
{
return "<p>ID: " . $this->id . "</p><p>Name: " . $this->name . "</p><p>RGB: " . $this->rgb . "</p><p>CMY: " . $this->cmy . "</p><p>Wavelength: " . $this->wavelength . "</p><p>Frequency: " . $this->frequency . "</p>";
}
and 2'nd class looks like
class CRUD_color
{
public function create_color($parameters)
{
$color=new color();
$color->setName($parameter['name']);
$color->setRGB($parameter['rgb']);
$color->setCMY($parameter['cmy']);
$color->setWavelength($parameter['wavelength']);
$color->setFrequency($parameter['frequency']);
$entitymanager->persist($color);
$entitymanager->flush();
}
public function request_color($parameters)
{
$color=$entitymanager->find($parameter['id']);
echo $color->toJSON($parameter['name']);
}
public function update_color($parameters)
{
$color=$entitymanager->find($parameter['id']);
$color->setName($parameter['name']);
$color->setRGB($parameter['rgb']);
$color->setCMY($parameter['cmy']);
$color->setWavelength($parameter['wavelength']);
$color->setFrequency($parameter['frequency']);
$entitymanager->persist($color);
$entitymanager->flush();
}
public function delete_color($parameters)
{
$color=$entitymanager->delete($parameter['id']);
}
}
now my question is if maybe it is better to have only one class color and include the functions from the second class in the 1st one? or let them apart?
why is one better than the other or vice versa? the design pattern is important to me so why choose one over the other..
Is there a problem if lets say we have the function create_color in witch we instantianate the class itself like new color() ????
now my question is if maybe it is better to have only one class color
and include the functions from the second class in the 1st one?
No.
or let them apart?
Yes.
why is one better than the other or vice versa?
If you decide to have different types of CRUDs or other objects that manipulate with colors (Builder for example) you need class Color to be a separate one. The same if you would like your CRUD to manipulate not only with Color objects. It is better to make decoupling as much as possible.
the design pattern is important to me so why choose one over the
other..
There are many patterns that can be helpful for you: Builder, Repository, Decorator, Bridge, Factory... It depends on your needs what is better to implement. You must be familiar with all of them and never implement it without understanding of why it is best choice for this particular task.
Is there a problem if lets say we have the function create_color in
witch we instantianate the class itself like new color() ????
Yes, if you need to add some creational step (for example generate ID in different way) you have to add this step to all your classes like Color, Font etc. In case of separate builder class - you add this step to create() method and it will generate ID in new way for all abstract objects.
Hope this will show you a way to learn more about patterns. Good luck!
BTW, take a look at this great free book: http://gameprogrammingpatterns.com/
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();
}
}
I'm trying to namespace my plugin functions by using a class and static functions. I'm getting the error:
Fatal error: Constructor Read_Time::read_time() cannot be static in /Applications/MAMP/htdocs/Wordpress/wp-content/plugins/readtime/readtime.php on line 41
class Read_Time {
public $options;
static public function init() {
add_filter('wp_meta', __CLASS__ . '::post_text');
}
static private function post_text() {
if(is_single()) {
global $post;
$content = $post->post_content;
echo("<h1>" . self::read_time($content) . "</h1>");
}
}
static private function word_count($to_count) {
return str_word_count($to_count);
}
static private function read_time($content) {
$wpm = 200;
$int_minutes = ceil( self::word_count($content) / $wpm );
if($int_minutes == 1) {
return $int_minutes . " minute";
}
else {
return $int_minutes . " minutes";
}
}
}
add_action('init', 'Read_Time::init');
Can someone tell me what I'm doing wrong?
PHP is interpreting your method read_time as a constructor for the class Read_Time, because it is not case-sensitive. The constructor cannot be static.
From the online documentation:
As of PHP 5.3.3, methods with the same name as the last element of a namespaced class name will no longer be treated as constructor. This change doesn't affect non-namespaced classes.
Example #2 Constructors in namespaced classes
<?php
namespace Foo;
class Bar {
public function Bar() {
// treated as constructor in PHP 5.3.0-5.3.2
// treated as regular method as of PHP 5.3.3
}
}
?>
P.S. If you really are using a version of PHP < 5.3.3, you should strongly consider upgrading. A lot has changed, and older versions may have unpatched bugs.
I have a File class, with a ->deleteOnExit function. If this function is called, the file will delete itself on shutdown. The File class uses the register_shutdown_function function to achieve this, deleting the file when the shutdown handler is ran. Question is, how can i test this in PHPUnit. I tried this
$file = new File(__DIR__ . "/delete_on_exit.txt");
$file->createFile();
$file->deleteOnExit();
register_shutdown_function(function() use ($file) {
$this->assertFalse($file->exists());
});
But that did not work. I guess that is because PHPUnit is already done with its testing by the time the function registered with register_shutdown_function is called.
Is there a way to do this?
You should probably use tmpfile() (documentation) internally instead of deleting the file with a shutdown function.
Also, the way PHPUnit works, you cannot test proper shutdown deletion. PHPUnit tests run in a single huge "application" - any shutdown function will only ever be called when the last test finishes and any other tasks like generating code coverage have been done. At this time, it is way too late for your test to report that the file indeed has been deleted.
Additionally, because of this I'd have my doubts whether or not your code will reliably delete the file, because any other registered shutdown function that simply calls exit() would prevent your shutdown function to delete the file. All these problems seem to not exist when using tmpfile(), because that is a OS supported call that will delete the file if the process opening it dies for whatever reason.
For the general purpose of unit testing php_register_callback_function, this is my hack :
<?php
namespace PAG\Testing;
class BlackboxedScriptRunner
{
public static $php = "/usr/bin/php";
public static function fetchScriptStdout($script): string
{
return self::executeFile($script, ' 2>/dev/null');
}
private static function executeFile($script, $option): string
{
if (php_sapi_name() !== "cli")
throw new RuntimeException("Cannot use this function in this setting for security reasons");
return shell_exec(self::$php ." -f $script -- $option");
}
public static function fetchScriptStderr($script): string
{
return self::executeFile($script, ' 2>&1 > /dev/null');
}
}
and the test :
<?php
use PAG\Testing\BlackboxedScriptRunner;
use PHPUnit\Framework\TestCase;
class ShutdownEventHandlerTest extends TestCase
{
public function testRegisterShutdownHandler()
{
$output =
BlackboxedScriptRunner::fetchScriptStdout(__DIR__ . "/../ManualTesting/testRegisterShutdownHandler.php");
$this->assertEquals(
file_get_contents(__DIR__ . "/../ManualTesting/testRegisterShutdownHandler.txt"),
$output);
}
}
Now I'm not saying this is awesome, I am saying this is awesome, I am saying it allows you to check the output of a whole script.
Warning : this is worse than eval, do not use for other purposes than testing.
And I am in a joyful mood, so here is my so called RegisterShutdownHandler :
<?php
namespace PAG\Shutdown;
class ShutdownEventHandler
{
private static $shutdown;
private static $shutdown_error;
private static $initialized = false;
private static $context = [];
public static function registerShutdownHandler($identifier, $function)
{
self::ensureInitialization();
self::$shutdown[$identifier] = $function;
}
private static function ensureInitialization(): void
{
if (!self::$initialized) {
self::$initialized = true;
self::registerShutdown();
}
}
private static function registerShutdown(): void
{
register_shutdown_function(function () {
self::shutdown();
});
}
private static function shutdown()
{
if (!is_null($error = error_get_last())) {
self::runCallbacks(self::$shutdown_error, $error);
}
self::runCallbacks(self::$shutdown);
}
private static function runCallbacks($array, $arguments = []): void
{
foreach ($array as $function) {
call_user_func($function, self::$context, $arguments);
}
}
public static function registerErrorShutdownHandler($identifier, $function)
{
self::ensureInitialization();
self::$shutdown_error[$identifier] = $function;
}
public static function deleteShutdownHandler($identifier)
{
unset(self::$shutdown[$identifier]);
}
public static function deleteErrorShutdownHandler($identifier)
{
unset(self::$shutdown_error[$identifier]);
}
public static function setContext(array $context): void
{
self::$context = $context;
}
}
You can find more of that on github. Must be frank though, it still has poor coverage.