I have two static values: "type" and "typeID". Type is human readable and constant, and typeID needs to be looked up from the database, based on the value of type. I need the lookup to happen once, when the class definition is first loaded
To illustrate, here is some code that doesn't work because you can't call functions in the declaration space.
MyClass extends BaseClass {
protected static $type = "communities";
protected static $typeID = MyClass::lookupTypeID(self::$type);
}
Is there a magic method that is called exactly once when the class definition is loaded? If there is something obvious I'm missing it.
shamelessly pulled from the php manual's static keyword comments:
Because php does not have a static constructor and you may want to initialize static class vars, there is one easy way, just call your own function directly after the class definition.
for example.
<?php
function Demonstration()
{
return 'This is the result of demonstration()';
}
class MyStaticClass
{
//public static $MyStaticVar = Demonstration(); //!!! FAILS: syntax error
public static $MyStaticVar = null;
public static function MyStaticInit()
{
//this is the static constructor
//because in a function, everything is allowed, including initializing using other functions
self::$MyStaticVar = Demonstration();
}
} MyStaticClass::MyStaticInit(); //Call the static constructor
echo MyStaticClass::$MyStaticVar;
//This is the result of demonstration()
?>
Simple and no magic needed, don't forget you can always define a variable as null and test that it is null (doing the db call only then). Then it's just a matter if you want that to happen when the class is constructed or included (include_once etc...)
MyClass extends BaseClass {
protected static $type = "communities";
protected static $typeID = null;
public function __construct(){
if(is_null(self::$typeID)){
self::lookupTypeID(self::$type);
}
}
public static lookupTypeID($type){
self::$typeID = //result of database query
}
}
or
MyClass::lookupTypeID(); //call static function when class file is included (global space)
MyClass extends BaseClass {
protected static $type = "communities";
protected static $typeID = null;
public function __construct(){
}
public static lookupTypeID($type=null){
if(is_null($type)){
$type = self::$type;
}
self::$typeID = //result of database query (SELECT somefield FROM sometable WHERE type=$type) etc..
}
}
a static constructor is more like a factory method
if(!function_exists(build_myclass)){
function build_myclass(){
return MyClass::build();
}
}
MyClass extends BaseClass {
protected static $type = "communities";
protected static $typeID = null;
public function __construct(){
}
public static function build(){
return new self(); //goes to __construct();
}
}
$class = new MyClass(); //or
$class = MyClass::build(); //or
$class = build_myclass();
Such a thing would normally be called a "static constructor", but PHP lacks such things. You might want to consider one of the workarounds suggested in the PHP manual comments, e.g. http://www.php.net/manual/en/language.oop5.static.php#95217
Related
Here's an example
class derp {
public static $importantVariable = "default";
public function doSomethingImportant() {
echo self::$importantVariable;
}
}
class fancy extends derp {
public static $importantVariable = "special";
}
$fancyInstance = new fancy();
$fancyInstance->doSomethingImportant();
Ok so, I want to override the static member "importantVariable" and make it so that inherited functions use the overriden value instead of the base class value.
In this case it says "default" but I want it to say "special", how do I make it refer to the overriden value?
<?php
class derp {
public static $importantVariable = "default";
public function doSomethingImportant() {
echo static::$importantVariable; //HERE
}
}
class fancy extends derp {
public static $importantVariable = "special";
}
$fancyInstance = new fancy();
$fancyInstance->doSomethingImportant();
http://php.net/manual/en/language.oop5.late-static-bindings.php
Instead of self, use static:
class derp {
public static $importantVariable = "default";
public function doSomethingImportant() {
echo static::$importantVariable;
}
}
The static keyword takes advantage of late static binding so that it will take on the value defined in the concrete class being used at runtime rather than the class that the reference just happens to be made from, as is the case with the self keyword.
Here is an abstract class I have to use;
abstract class Model
{
protected static $_tableName = false;
public static function tableName()
{
return static::$_tableName;
}
public static function find($idOrWhere = false, $params = array(), $limit = false)
{
$sql = "SELECT * FROM " . static::tableName();
I can't seem to set the _tableName, static::tableName(), or tableName() in my own class;
class Payments extends Model {
public function __construct()
{
$this->_tableName = 'payments';
}
That's not doing anything! It's not set the tableName to payments. And I can't figure out how to use the method tableName() either.
You are trying to access _tableName in a non-static way (I.e. $this->_tablename) even though you've declared it as static.
You need to access it like so:
self::$_tableName
OR, for late static binding:
static::$_tableName
Overall, you should avoid the use of static classes as much as possible, mainly due to testing purposes.
Static members have to be accessed on the class, not on an instance:
class Payments extends Model {
public function __construct()
{
Payments::$_tableName = 'payments';
}
}
A property declared as static cannot be accessed with an instantiated class object (though a static method can).
Static properties cannot be accessed through the object using the arrow operator ->.
http://php.net/manual/en/language.oop5.static.php
Like any other PHP static variable, static properties may only be initialized using a literal or constant; expressions are not allowed.
According to this material your classes should be reworked that way.
abstract class Model
{
protected static $_tableName = false;
public static function find($idOrWhere = false, $params = array(), $limit = false)
{
$sql = "SELECT * FROM " . self::tableName();
...
}
private static function tableName()
{
if (!static::$_tableName) {
throw new \RuntimeException('No table name provided');
}
return static::$_tableName;
}
class Payments extends Model {
protected static $_tableName = 'payments';
}
Btw, __contruct is called on object instantiation and you should not set any static properties values there.
I have class DbTable, which implements all db queries to database such as insertRecord, updateRecord, ... But variable is not rewriting.
abstract class DbTable {
public static $table;
public static function insertRecord($data) {
// here I add some values to data, but that's not important
my_db::insert(self::$table, $data);
}
}
class User extends DbTable {
public static $table = 'table_users';
}
// everywhere I can call
User::insertRecord($data);
I know I can call
$c = get_called_class();
my_db::insert($c::$table, $data);
but I think that's not best solution at all.
Method and variables can be non static, I just use them because it is comfortable to write User::insertRecord instead of $user = new User(); $user->insertRecord($data);
When you're working with static classes you need to specify your variable source, in this case you're scoping to both classes and not on single class, this makes a difference, because self is scoping to concurrent class and when you want to scope for both classes you have to use static.
/**
* For testing
*/
class my_db {
public static function insert($table, $data){
echo $table;
}
}
abstract class DbTable {
public static $table = null;
public static function insertRecord($data) {
//self::$table is empty
//static::$table has 'table_users'
// here I add some values to data, but that's not important
my_db::insert(static::$table, $data);
}
}
class User extends DbTable {
public static $table = 'table_users';
}
// everywhere I can call
User::insertRecord(['Hi']);
self::$table is empty
static::$table has 'table_users'
You can read more about this here: SO Answer and PHP Documentation
Use static variables are unnecessary in this case. You just need dynamically create User object and them call method.
abstract class DbTable
{
protected $tableName;
public static function insertRecord($data)
{
$object = static::newInstance();
$object->insert($data);
}
public static function newInstance()
{
$className = get_called_class();
return new $className();
}
public function insert($data)
{
my_db::insert($this->tableName, $data);
}
}
class User extends DbTable
{
public function __construct()
{
$this->tableName = 'table_users';
}
}
You can now call:
User::insertRecord(['col1' => 'val1']);
But also you can insert rows from instated object:
$user = new User();
$user->insert(['col1' => 'val1']);
what I'm trying to achieve (PHP 5.3) is to have an accessor to my representation of, for example, the HTML Body of a page. Instead of echoing everything directly it should be added to an array of entries in that singleton. Example: myBodyClass::add('<h1>Title</h1>');
add() is declared as public static function add($strEntry) {}
Now should I just add them to a static array $entries like self::$entries[] = $strEntry; (class VersionB) or should I use an instance like self::getInstance()->entries[] = $strEntry;? (class VersionA) (whereby getInstance() would of course instanciate ´...new self;´ if necessary)
I don't quite understand the difference yet, I'm afraid.
The second part of my question is how to print the object. The PHP manual is a bit thin about why __toString() cannot be static - but then again I would understand a parser to have a problem distinguishing echo myBodyClass from a constant (so is that the reason?)
Ideally I would like to call add() as often as needed to add all parts of the body, and then use something like echo myHeaderClass, myBodyClass, myFooterClass; at the end of the script, which should invoke the __toString() methods within the classes.
Thanks for pointing me into the correct direction.
Code Example
class VersionA
{
private static $instance = null;
private $entries = array();
private final function __construct(){}
private final function __clone(){}
private static final function getInstance()
{
if (self::$instance === null) :
self::$instance = new self;
endif;
return self::$instance;
}
public static function add($sString)
{
self::getInstance()->entries[] = $sString;
}
public static function getHtml()
{
return implode("\r\n", self::getInstance()->entries);
}
}
class VersionB
{
private static $entries = array();
private final function __construct(){}
private final function __clone(){}
public static function add($sString)
{
self::$entries[] = $sString;
}
public static function getHtml()
{
return implode("\r\n", self::$entries);
}
}
(Copied from comments, as requested by OP...)
You're missing the point of a singleton. There is a difference between a singleton object and a static class. If you want to use methods that act on an object (like __toString()), then you need it to be an object; a static class isn't good enough. If you want to avoid calling getInstance all the time, then set a variable to the object, and pass it around everywhere like you would with other objects, per the Dependency Injection pattern. That would probably be best practice advice anyway.
The thing with a static class is that it isn't really OOP; it's just a bunch of global functions with a shared class name. One may as well use plain functions with a namespace declaration.
But the main reason for using a genuine singleton is swappability. Assuming you follow my advice above and create a single reference to the object that you pass around your code, it becomes a lot easier to swap in an alternative object since you don't have the hard-coded class name being referenced all over the place. This makes it a lot easier to write decent unit tests for your code that uses the class.
Hope that helps.
You should probably not use a static add method.
The idea of a singleton is that you create a single instance of a class so that external objects can interact with that instance. That means that your add method should not be static.
You could do something like:
class MyBodyClass
{
protected $entries = array();
protected $instance;
public static function getInstance()
{
if (is_null($this->instance)) {
$this->instance = new self();
}
return $this->instance;
}
private function __construct() {}
public function add($strEntry)
{
$this->entires[] = $strEntry;
}
}
And call it like this:
MyBodyClass::getInstance()->add('<h1>blah</h1>');
Something like this should work:
class MySingleton
{
public static function getInstance()
{
static $inst = null;
if ($inst === null) {
$inst = new MySingleton();
}
return $inst;
}
private function __construct() { }
public static function add() {}
public function __toString() {
echo 'Something';
}
}
$body = MySingleton::getInstance();
$body::add('Something');
echo $body;
This is what I have: All objects that can be persisted on the database extend the DatabaseObject abstract class, which has all the logic code to actually watch for attribute changes and run the databas queries.
I'm using two static variables to define object-specific details. I define them generically in the base class, and then supposedly I overwrite them in the actual database objects.
The problem is: When the code in the parent class is actually executed, it uses the old parent value instead of the current object value.
Here's the code for the base class:
abstract class DatabaseObject {
public $id;
private static $databaseTable = NULL;
private static $databaseFields = array();
private $data = array();
private $changedFields = array();
public function IDatabaseObject($id) {
$this->id = $id;
$this->data = Database::GetSingle(self::$databaseTable, $id);
Utils::copyToObject($this, $this->data, self::$databaseFields);
}
public static function Load($id) {
return new self($userID);
}
public static function Create($data) {
$id = Database::Insert(self::$databaseTable, $data);
return new self($id);
}
public function Save() {
$data = Utils::copyFromObject($this, $this->changedFields);
Database::Update(self::$databaseTable, $data, $this->id);
}
public function __constructor() {
// We do this to allow __get and __set to be called on public vars
foreach(self::$databaseFields as $field) {
unset($this->$field);
}
}
public function __get($variableName) {
return $this->$variableName;
}
public function __set($variableName, $variableValue) {
// We only want to update what has been changed
if(!in_array($variableName, $this->changedFields) && in_array($variableName, self::$databaseFields)) {
array_push($this->changedFields, $variableName);
}
$this->$variableName = $variableValue;
}
}
And here's the code for one of the objects extending the base class above:
class Client extends DatabaseObject {
public static $databaseTable = "clients";
public static $databaseFields = array("name","contactName","primaryUserID","email","is_active","rg","cpf","cnpj","ie","addrType","addrName","addrNumber","addrComplement","addrPostalCode","addrNeighborhood","addrCity","addrState","addrCountry","phoneLandline","phoneFax","phoneMobile");
public $name;
public $contactName;
public $primaryUserID;
public $email;
public $is_active;
public $rg;
public $cpf;
public $cnpj;
public $ie;
public $addrType;
public $addrName;
public $addrNumber;
public $addrComplement;
public $addrPostalCode;
public $addrNeighborhood;
public $addrCity;
public $addrState;
public $addrCountry;
public $phoneLandline;
public $phoneFax;
public $phoneMobile;
public static function Load($id) {
return new Client($id);
}
}
What am I doing wrong here? Is there another way I can achieve the same result?
A brief addendum: I declare the attributes in the class body mainly to let it be seen by the NetBeans' auto-complete feature.
You are looking for Late Static Binding.
So you need to use:
static::$databaseTable
instead of
self::$databaseTable
This feature is available as of PHP 5.3. Simulating this in PHP 5.2 is very hard, because of two reasons: get_called_class is available only since PHP 5.3, too. Therefore it must be simulated, too, using debug_backtrace. The second problem is, that if you have the called class, you still may not use $calledClass::$property because this is a PHP 5.3 feature, too. Here you need to use eval or Reflection. So I do hope that you have PHP 5.3 ;)