I have some PHP snippets for an application I am trying to restrict inputs coming from a request in the front end of my JavaScript application. The page sends a request using JSON object which contains a field value present that I assign as 'Open', 'Complete', or 'Closed'. I want to prevent unwanted input tampering or values to be sent through.
Question:
Below property $eventstatus is type hinted with the enum, but when I assign the string value inside $array['EventStatus'] PHP (7.4.9) reports an error that my types are not compatible. It needs to see a Status type when in fact I am assigning it a string.
How do I fix this?
$event->eventstatus = $array['EventStatus'];
Enum class (Status)
<?php
namespace app\enums;
abstract class Status
{
const Open = 'Open';
const Complete = 'Complete';
const Closed = 'Closed';
}
Mapper Class Member Function - snippet, code below takes an array value and maps it to a class property
<?php
function mapFromArray($event, $array) {
if (!is_null($array['EventStatus'])) $event->eventstatus = $array['EventStatus'];
}
Model Class
<?php
namespace data\model;
use app\enums\Status;
class Event
{
public $eventid;
public $riskid;
public $eventtitle;
public Status $eventstatus;
}
Your type hint actually tells PHP that you expect $eventstatus to be an instance of Status. But the values are actually just simple strings: 'Open', 'Complete' and 'Closed'.
So the correct type hint would be:
<?php
namespace data\model;
use app\enums\Status;
class Event
{
// ...
public string $eventstatus;
}
But with this PHP accepts any string and not only a "valid" one. Using proper Enums here would help but currently PHP 7 has no native support for Enums (which is implemented for PHP 8.1 though).
If you want to use the Status class for more readable code you can just change the type hint to string.
If you want to validate the input data you could extend the code like this:
<?php
namespace app\enums;
abstract class Status
{
const Open = 'Open';
const Complete = 'Complete';
const Closed = 'Closed';
const Valid_Statuses = [
self::Open,
self::Complete,
self::Closed,
];
}
function mapFromArray($event, $array) {
if (!is_null($array['EventStatus'])) {
if (in_array($array['EventStatus'], Status::Valid_Statuses)) {
$event->eventstatus = $array['EventStatus'];
} else {
// handle invalid status value here
}
}
}
If you want to use strict type hinting to ensure validity everywhere you'd need to wrap the value into a instance of the class, e.g.:
namespace app\enums;
abstract class Status
{
const Open = 'Open';
const Complete = 'Complete';
const Closed = 'Closed';
const Valid_Statuses = [
self::Open,
self::Complete,
self::Closed,
];
private string $value;
public function __construct(string $value) {
if (!in_array($value, self::Valid_Statuses)) {
throw \InvalidArgumentException(sprintf('Invalid status "%s"', $value));
}
$this->value = $value;
}
public function getValue(): string {
return $this->value;
}
public function __toString(): string {
return $this->value;
}
}
function mapFromArray($event, $array) {
if (!is_null($array['EventStatus'])) {
try {
$event->eventstatus = new Status($array['EventStatus']);
} catch (\Exception $exception) {
// handle invalid status value here
}
}
}
I tried a slightly different method from what was proposed using array values, but still relying on some sort of array to check for allowed values.
In my Events class I extended from abstract class Mapper (within which I added a new performMapping function to make mapping more dynamic)
<?php
namespace data\mapper;
use app\enums\Status;
use data\model\Event;
class Events extends Mapper
{
public function mapFromArray($array) : Event
{
$event = $this->_performMapping($array, new Event());
return $event;
}
}
Model - Added Magic Methods (__set, __get)
<?php
namespace data\model;
use app\enums\Status;
class Event
{
public $eventid;
public $riskid;
public $eventtitle;
private $eventstatus;
public $eventownerid;
public $actualdate;
public $scheduledate;
public $baselinedate;
public $actuallikelihood;
public $actualtechnical;
public $actualschedule;
public $actualcost;
public $scheduledlikelihood;
public $scheduledtechnical;
public $scheduledschedule;
public $scheduledcost;
public $baselinelikelihood;
public $baselinetechnical;
public $baselineschedule;
public $baselinecost;
public function __set($name, $value)
{
switch ($name)
{
case 'eventstatus':
{
$class = Status::class;
try
{
$reflection = new \ReflectionClass($class);
}
catch (\ReflectionException $ex)
{
return null;
}
$constants = $reflection->getConstants();
if (array_key_exists($value, $constants))
$this->$name = constant("\\".$class."::$constants[$value]");
else
throw (new \Exception("Property $name not found in " . $class));
}
default:
{
if (property_exists(get_class($this), $name))
$this->$name = $value;
else
throw (new \Exception("Property $name not found in " . get_class($this)));
}
}
}
public function __get($name)
{
switch ($name)
{
case 'eventstatus':
return $this->$name;
default:
if (property_exists($this, $name))
return $this->$name;
else
return null;
}
}
}
Mapper
<?php
namespace data\mapper;
abstract class mapper
{
protected $db = null;
public function __construct(\PDO $db)
{
$this->db = $db;
}
abstract public function mapFromArray($array);
protected function _populateFromCollection($results = null)
{
$return = [];
if ($results != null)
{
foreach($results as $result)
{
$return[] = $this->mapFromArray($result);
}
}
return $return;
}
protected function _performMapping($array, $object)
{
foreach (array_keys($array) as $property)
{
$lowerCaseProperty = strtolower($property);
if (property_exists(get_class($object), $property))
$object->$property = $array[$property];
else if (property_exists(get_class($object), $lowerCaseProperty))
$object->$lowerCaseProperty = $array[$property];
}
return $object;
}
Enum
<?php
namespace app\enums;
abstract class Status
{
const Open = 'Open';
const Complete = 'Complete';
const Closed = 'Closed';
}
So I am facing this problem. I have a class representing a record in my database (User in this example). The class has as many properties as the database table has columns. For simplicity, I have just three in my example:
$id - ID of the user (must be set to a positive integer for registered user, might be set to 0 for user objects that aren't saved in the database yet)
$name - Name of user (must be set for every user, but before loading it from the database might be undefined)
$email - E-mail address of the user (might be NULL in case the user didn't submit an e-mail address)
My (simplified) class looks like this:
<?php
class User
{
private $id;
private $name;
private $email;
public function __construct(int $id = 0)
{
if (!empty($id)){ $this->id = $id; }
//If $id === 0, it means that the record represented by this instance isn't saved in the database yet and the property will be filled after calling the save() method
}
public function initialize(string $name = '', $email = '')
{
//If any of the parameters isn't specified, prevent overwriting curent values
if ($name === ''){ $name = $this->name; }
if ($email === ''){ $email = $this->email; }
$this->name = $name;
$this->email = $email;
}
public function load()
{
if (!empty($this->id))
{
//Load name and e-mail from the database and save them into properties
}
}
public function save()
{
if (!empty($this->id))
{
//Update existing user record in the database
}
else
{
//Insert a new record into the table and set $this->id to the ID of the last inserted row
}
}
public function isFullyLoaded()
{
$properties = get_object_vars($this);
foreach ($properties as $property)
{
if (!isset($property)){ return false; } //TODO - REPLACE isset() WITH SOMETHING ELSE
}
return true;
}
//Getters like getName() and getId() would come here
}
Now finally to my problem. As you can see, the instance of this class can be created without all properties set. That's a problem in case I want to e. g. call getName() while the name isn't known yet (it wasn't set via the initialize() method and load() wasn't called). For that, I wrote method isFullyLoaded() which checks if all properties are known and if not, load() should be called (from the method calling isFullyLoaded(). And the core of the problem is, that some variables might be empty strings (''), zero values (0) or even null (like the $email property). So I want to distinguish variables that have any value set (including null) and those who have never been assigned any value.
Specific example: I want to achieve this code:
$user1 = new User(1);
$user1->initialize('Vic', 'nerd.from.klamath#fallout2.com');
var_dump($user1->isFullyLoaded());
$user2 = new User(2);
$user2->initialize('Cassidy', null); //No e-mail was specified during the registration
var_dump($user2->isFullyLoaded());
$user3 = new User(3);
$user3->initialize('Myron'); //E-mail isn't known yet, but might be saved in the database
var_dump($user3->isFullyLoaded());
to output this:
bool(true)
bool(true)
bool(false)
TL:DR How do distinguish undefined variable and variable which has been assigned NULL in PHP?
Here is another way to introduce the custom Undefined class (as singleton). Additionally, be sure that your class properties are typed:
class Undefined
{
private static Undefined $instance;
protected function __constructor()
{
}
protected function __clone()
{
}
public function __wakeup()
{
throw new Exception("Not allowed for a singleton.");
}
static function getInstance(): Undefined
{
return self::$instance ?? (self::$instance = new static());
}
}
class Person
{
private int $age;
public function getAge(): int|Undefined
{
return $this->age ?? Undefined::getInstance();
}
}
$person = new Person();
if ($person->getAge() instanceof Undefined) {
// do something
}
But there is a downside with using singleton pattern, because all the undefined objects in your app will be strictly equal to each other. Otherwise, every get operation returning undefined value will have a side effect namely another piece of allocated RAM.
PHP has not the value undefined like javascript. But it is not strict typed so if you do not find a better solution here is one with an custom type UNDEFINED
<?php
class UNDEFINED { }
class Test {
var $a;
function __construct( $a='' ) {
$this->a = new UNDEFINED();
if( $a !== '' ) {
$this->a = $a;
}
}
function isDefined() {
$result =true;
if(gettype($this->a) === 'object'){
if(get_class($this->a) === 'UNDEFINED') {
$result=false;
}
}
echo gettype($this->a) . get_class($this->a);
return $result;
}
}
$test= new Test();
$test->isDefined();
Here is a may be litte better version which used instanceof instead of get_call and getType
<?php
class UNDEFINED { }
class Test {
var $id;
var $a;
var $b;
function __construct( $id) {
$this->id = $id;
$this->a = new UNDEFINED();
$this->b = new UNDEFINED();
}
function init( $a = '' , $b = '') {
$this->a = $this->setValue($a,$this->a);
$this->b = $this->setValue($b,$this->b);
}
function setValue($a,$default) {
return $a === '' ? $default : $a;
}
function isUndefined($a) {
return $a instanceof UNDEFINED;
}
public function isFullyLoaded()
{
$result = true;
$properties = get_object_vars($this);
print_r($properties);
foreach ($properties as $property){
$result = $result && !$this->isUndefined($property);
if ( !$result) break;
}
return $result;
}
function printStatus() {
if($this->isFullyLoaded() ) {
echo 'Loaded!';
} else {
echo 'Not loaded';
}
}
}
$test= new Test(1);
$test->printStatus();
$test->init('hello');
$test->printStatus();
$test->init('', null);
$test->printStatus();
Use property_exists():
<?php
error_reporting(E_ALL);
// oop:
class A {
public $null_var = null;
}
$a = new A;
if(property_exists($a, 'null_var')) {
echo "null_var property exists\n";
}
if(property_exists($a, 'unset_var')) {
echo "unset_var property exists\n";
}
// procedural:
$null_var = null;
if(array_key_exists('null_var', $GLOBALS)) {
echo "null_var variable exists\n";
}
if(array_key_exists('unset_var', $GLOBALS)) {
echo "unset_var variable exists\n";
}
// output:
// null_var property exists
// null_var variable exists
How can I implement the named argument feature in PHP 7+?
The ideal syntax is:
find($wildcard, relative = true, listIfEmpty = false) {
...
}
But there is no such solution.
In the answer, I implemented the shortest possible solution that supports:
Refactoring
Type Hints for the params and function result
Reusable param set
Typesafe params
Default values for params
Getter for each param ( no hard-coded string )
Very simple setter/getter that you only change one name for each param
See the answer for more information and implementation
First, do it once in your Framework, or globally:
// the superclass that every params class will extend this
class params {
protected function setter($key, $value) {
if ($value === null) {
return $this->{$key};
}
$this->{$key} = $value;
return $this;
}
}
Then, do this for each function that requires named arguments with proper names:
// define a helper method for simple instantiation
function params_find() { return new params_find(); }
// a params class that will use from find() method
class params_find extends params {
// list of params with default values
// below list is just a sample
protected $listIfEmpty = false;
protected $structured = true;
protected $relative = false;
protected $prefix = "";
protected $justFileNames = false;
// for each param duplicate line and just change function name exactly matching param name. setter/getter will done automatically
function listIfEmpty($val = null) { return $this->setter(__FUNCTION__, $val); }
function structured($val = null) { return $this->setter(__FUNCTION__, $val); }
function relative($val = null) { return $this->setter(__FUNCTION__, $val); }
function prefix($val = null) { return $this->setter(__FUNCTION__, $val); }
function justFileNames($val = null) { return $this->setter(__FUNCTION__, $val); }
}
After that, write your function using this feature:
// your function that uses named arguments
// in this example $wildcard argument is required
function find($wildcard = [], params_find $opt = null) {
if ($opt === null) {
$opt = params_find();
}
// do something with your params like this
if ($opt->structured()) {
// ...
}
return $something_if_you_want;
}
Finally, use it with the simplest interface:
// finally use your function with some params ( other params will have default values )
$files = find("*.css", params_find()
->structured(false)
->listIfEmpty(true)
->prefix("something")
);
// if you need all default values
$files = find("*.css");
// reusable options
$opt = params_find()
->listIfEmpty(false)
->prefix("something")
)
$files1 = find("*.css", $opt);
$files2 = find("*.html", $opt);
$files3 = find("*.scss", $opt
->structured(true)
);
It's a PHP 8 >= feature now:
https://wiki.php.net/rfc/named_params
<?php
// Using positional arguments:
array_fill(0, 100, 50);
// Using named arguments:
array_fill(start_index: 0, num: 100, value: 50);
I am having a problem with the filtering and validation of the form below. If the two mobile numbers are entered in national format (07777654321) the form validation fails, but if they are entered in international format (447777654321) it validates. My understanding is that the filters will be applied first so when identical is applied both mobile and mobile_confirm should have been processed with the same set of filters.
So if I enter 07777654321 into both fields it will get filtered to 447777654321 in both fields which should then validate. Where am I going wrong.
I am using ZF 1.11.3.
<?php
class Test_Form_Check extends Zend_Form
{
public function init()
{
$mobile= new Zend_Form_Element_Text( 'mobile' );
$mobile->addFilter(new Test_Filter_MobileFilter('44'))
->addValidator(new Test_Validate_Mobile())
->setRequired( true );
$mobile1= new Zend_Form_Element_Text( 'mobile_confirm' );
$mobile1->addFilter(new Test_Filter_MobileFilter('44'))
->addValidator(new Test_Validate_Mobile())
->addValidator('Identical', false, array('token' => 'mobile', 'messages' => 'Mobile numbers do not match.'))
->setRequired( true );
$Add = new Zend_Form_Element_Submit('Add');
$Add->setLabel('Submit');
$this->addElement($mobile)
->addElement($mobile1)
->addElement( $Add );
}
}
class Test_Validate_Mobile extends Zend_Validate_Abstract
{
const NOT_DIGITS = 'notDigits';
const STRING_EMPTY = 'digitsStringEmpty';
const INVALID = 'digitsInvalid';
const INVALIDPHONE = 'phonenumberinvalid';
protected static $_filter = null;
protected $_messageTemplates = array(
self::NOT_DIGITS => "'%value%' must contain only digits",
self::STRING_EMPTY => "'%value%' is an empty string",
self::INVALID => "Invalid type given. String, integer or float expected",
self::INVALIDPHONE => "Invalid number, try with country code",
);
public function isValid($value)
{
if (!is_string($value) && !is_int($value) && !is_float($value)) {
$this->_error(self::INVALID);
return false;
}
if (!preg_match('/^(447)[0-9]{9}$/', $value)) {
$this->_error(self::INVALIDPHONE);
return false;
}
$this->_setValue((string) $value);
if ('' === $this->_value) {
$this->_error(self::STRING_EMPTY);
return false;
}
if (null === self::$_filter) {
require_once 'Zend/Filter/Digits.php';
self::$_filter = new Zend_Filter_Digits();
}
if ($this->_value !== self::$_filter->filter($this->_value)) {
$this->_error(self::NOT_DIGITS);
return false;
}
return true;
}
}
class Test_Filter_MobileFilter implements Zend_Filter_Interface
{
/**
* Default country code
*
* #var string
*/
protected $_def_country = '44';
public function __construct($options = null)
{
if ($options !== null) {
if (is_string($options) && is_numeric($options)) {
$this->_def_country = $options;
}
else {
require_once 'Zend/Filter/Exception.php';
throw new Zend_Filter_Exception('Options not valid country code');
}
}
}
public function filter($value)
{
if (!empty($value)) {
$replace = array(' ','+','-','(',')');
$value = str_replace($replace, '', $value);
$value = preg_replace('/\A0/', $this->_def_country, $value);
}
return $value;
}
}
I will be very grateful for any help with this. It is driving me round the bend.
I guess problem is with Zend_Validate_Identical validator , it uses unfiltered raw value from $_POST. Just quick fix:
class My_Validate_Identical extends Zend_Validate_Identical
{
/**
*
* #var Zend_Form_Element
*/
protected $_element = null;
public function __construct($token = null)
{
if (is_array($token) && array_key_exists('element', $token)) {
$this->_element = $token['element'];
}
parent::__construct($token);
}
public function isValid($value, $context = null)
{
$context[$this->getToken()] = $this->_element->getValue();
return parent::isValid($value, $context);
}
}
Attaching validator:
$mobile1->addValidator(new My_Validate_Identical(array(
'token' => 'mobile',
'messages' => 'Mobile numbers do not match.',
'element' => $mobile
)))
Hope this helps :)
You can't put two __construct functions with unique argument signatures in a PHP class. I'd like to do this:
class Student
{
protected $id;
protected $name;
// etc.
public function __construct($id){
$this->id = $id;
// other members are still uninitialized
}
public function __construct($row_from_database){
$this->id = $row_from_database->id;
$this->name = $row_from_database->name;
// etc.
}
}
What is the best way to do this in PHP?
I'd probably do something like this:
<?php
class Student
{
public function __construct() {
// allocate your stuff
}
public static function withID( $id ) {
$instance = new self();
$instance->loadByID( $id );
return $instance;
}
public static function withRow( array $row ) {
$instance = new self();
$instance->fill( $row );
return $instance;
}
protected function loadByID( $id ) {
// do query
$row = my_awesome_db_access_stuff( $id );
$this->fill( $row );
}
protected function fill( array $row ) {
// fill all properties from array
}
}
?>
Then if i want a Student where i know the ID:
$student = Student::withID( $id );
Or if i have an array of the db row:
$student = Student::withRow( $row );
Technically you're not building multiple constructors, just static helper methods, but you get to avoid a lot of spaghetti code in the constructor this way.
The solution of Kris is really nice, but I prefer a mix of factory and fluent style:
<?php
class Student
{
protected $firstName;
protected $lastName;
// etc.
/**
* Constructor
*/
public function __construct() {
// allocate your stuff
}
/**
* Static constructor / factory
*/
public static function create() {
return new self();
}
/**
* FirstName setter - fluent style
*/
public function setFirstName($firstName) {
$this->firstName = $firstName;
return $this;
}
/**
* LastName setter - fluent style
*/
public function setLastName($lastName) {
$this->lastName = $lastName;
return $this;
}
}
// create instance
$student= Student::create()->setFirstName("John")->setLastName("Doe");
// see result
var_dump($student);
?>
PHP is a dynamic language, so you can't overload methods. You have to check the types of your argument like this:
class Student
{
protected $id;
protected $name;
// etc.
public function __construct($idOrRow){
if(is_int($idOrRow))
{
$this->id = $idOrRow;
// other members are still uninitialized
}
else if(is_array($idOrRow))
{
$this->id = $idOrRow->id;
$this->name = $idOrRow->name;
// etc.
}
}
As has already been shown here, there are many ways of declaring multiple constructors in PHP, but none of them are the correct way of doing so (since PHP technically doesn't allow it).
But it doesn't stop us from hacking this functionality...
Here's another example:
<?php
class myClass {
public function __construct() {
$get_arguments = func_get_args();
$number_of_arguments = func_num_args();
if (method_exists($this, $method_name = '__construct'.$number_of_arguments)) {
call_user_func_array(array($this, $method_name), $get_arguments);
}
}
public function __construct1($argument1) {
echo 'constructor with 1 parameter ' . $argument1 . "\n";
}
public function __construct2($argument1, $argument2) {
echo 'constructor with 2 parameter ' . $argument1 . ' ' . $argument2 . "\n";
}
public function __construct3($argument1, $argument2, $argument3) {
echo 'constructor with 3 parameter ' . $argument1 . ' ' . $argument2 . ' ' . $argument3 . "\n";
}
}
$object1 = new myClass('BUET');
$object2 = new myClass('BUET', 'is');
$object3 = new myClass('BUET', 'is', 'Best.');
Source: The easiest way to use and understand multiple constructors:
Hope this helps. :)
public function __construct() {
$parameters = func_get_args();
...
}
$o = new MyClass('One', 'Two', 3);
Now $paramters will be an array with the values 'One', 'Two', 3.
Edit,
I can add that
func_num_args()
will give you the number of parameters to the function.
You could do something like this:
public function __construct($param)
{
if(is_int($param)) {
$this->id = $param;
} elseif(is_object($param)) {
// do something else
}
}
As of version 5.4, PHP supports traits. This is not exactly what you are looking for, but a simplistic trait based approach would be:
trait StudentTrait {
protected $id;
protected $name;
final public function setId($id) {
$this->id = $id;
return $this;
}
final public function getId() { return $this->id; }
final public function setName($name) {
$this->name = $name;
return $this;
}
final public function getName() { return $this->name; }
}
class Student1 {
use StudentTrait;
final public function __construct($id) { $this->setId($id); }
}
class Student2 {
use StudentTrait;
final public function __construct($id, $name) { $this->setId($id)->setName($name); }
}
We end up with two classes, one for each constructor, which is a bit counter-productive. To maintain some sanity, I'll throw in a factory:
class StudentFactory {
static public function getStudent($id, $name = null) {
return
is_null($name)
? new Student1($id)
: new Student2($id, $name)
}
}
So, it all comes down to this:
$student1 = StudentFactory::getStudent(1);
$student2 = StudentFactory::getStudent(1, "yannis");
It's a horribly verbose approach, but it can be extremely convenient.
Here is an elegant way to do it. Create trait that will enable multiple constructors given the number of parameters. You would simply add the number of parameters to the function name "__construct". So one parameter will be "__construct1", two "__construct2"... etc.
trait constructable
{
public function __construct()
{
$a = func_get_args();
$i = func_num_args();
if (method_exists($this,$f='__construct'.$i)) {
call_user_func_array([$this,$f],$a);
}
}
}
class a{
use constructable;
public $result;
public function __construct1($a){
$this->result = $a;
}
public function __construct2($a, $b){
$this->result = $a + $b;
}
}
echo (new a(1))->result; // 1
echo (new a(1,2))->result; // 3
Another option is to use default arguments in the constructor like this
class Student {
private $id;
private $name;
//...
public function __construct($id, $row=array()) {
$this->id = $id;
foreach($row as $key => $value) $this->$key = $value;
}
}
This means you'll need to instantiate with a row like this: $student = new Student($row['id'], $row) but keeps your constructor nice and clean.
On the other hand, if you want to make use of polymorphism then you can create two classes like so:
class Student {
public function __construct($row) {
foreach($row as $key => $value) $this->$key = $value;
}
}
class EmptyStudent extends Student {
public function __construct($id) {
parent::__construct(array('id' => $id));
}
}
as stated in the other comments, as php does not support overloading, usually the "type checking tricks" in constructor are avoided and the factory pattern is used intead
ie.
$myObj = MyClass::factory('fromInteger', $params);
$myObj = MyClass::factory('fromRow', $params);
You could do something like the following which is really easy and very clean:
public function __construct()
{
$arguments = func_get_args();
switch(sizeof(func_get_args()))
{
case 0: //No arguments
break;
case 1: //One argument
$this->do_something($arguments[0]);
break;
case 2: //Two arguments
$this->do_something_else($arguments[0], $arguments[1]);
break;
}
}
This question has already been answered with very smart ways to fulfil the requirement but I am wondering why not take a step back and ask the basic question of why do we need a class with two constructors?
If my class needs two constructors then probably the way I am designing my classes needs little more consideration to come up with a design that is cleaner and more testable.
We are trying to mix up how to instantiate a class with the actual class logic.
If a Student object is in a valid state, then does it matter if it was constructed from the row of a DB or data from a web form or a cli request?
Now to answer the question that that may arise here that if we don't add the logic of creating an object from db row, then how do we create an object from the db data, we can simply add another class, call it StudentMapper if you are comfortable with data mapper pattern, in some cases you can use StudentRepository, and if nothing fits your needs you can make a StudentFactory to handle all kinds of object construction tasks.
Bottomline is to keep persistence layer out of our head when we are working on the domain objects.
I know I'm super late to the party here, but I came up with a fairly flexible pattern that should allow some really interesting and versatile implementations.
Set up your class as you normally would, with whatever variables you like.
class MyClass{
protected $myVar1;
protected $myVar2;
public function __construct($obj = null){
if($obj){
foreach (((object)$obj) as $key => $value) {
if(isset($value) && in_array($key, array_keys(get_object_vars($this)))){
$this->$key = $value;
}
}
}
}
}
When you make your object just pass an associative array with the keys of the array the same as the names of your vars, like so...
$sample_variable = new MyClass([
'myVar2'=>123,
'i_dont_want_this_one'=> 'This won\'t make it into the class'
]);
print_r($sample_variable);
The print_r($sample_variable); after this instantiation yields the following:
MyClass Object ( [myVar1:protected] => [myVar2:protected] => 123 )
Because we've initialize $group to null in our __construct(...), it is also valid to pass nothing whatsoever into the constructor as well, like so...
$sample_variable = new MyClass();
print_r($sample_variable);
Now the output is exactly as expected:
MyClass Object ( [myVar1:protected] => [myVar2:protected] => )
The reason I wrote this was so that I could directly pass the output of json_decode(...) to my constructor, and not worry about it too much.
This was executed in PHP 7.1. Enjoy!
I was facing the same issue on creating multiple constructors with different signatures but unfortunately, PHP doesn't offer a direct method to do so. Howerever, I found a trick to overcome that. Hope works for all of you too.
<?PHP
class Animal
{
public function __construct()
{
$arguments = func_get_args();
$numberOfArguments = func_num_args();
if (method_exists($this, $function = '__construct'.$numberOfArguments)) {
call_user_func_array(array($this, $function), $arguments);
}
}
public function __construct1($a1)
{
echo('__construct with 1 param called: '.$a1.PHP_EOL);
}
public function __construct2($a1, $a2)
{
echo('__construct with 2 params called: '.$a1.','.$a2.PHP_EOL);
}
public function __construct3($a1, $a2, $a3)
{
echo('__construct with 3 params called: '.$a1.','.$a2.','.$a3.PHP_EOL);
}
}
$o = new Animal('sheep');
$o = new Animal('sheep','cat');
$o = new Animal('sheep','cat','dog');
// __construct with 1 param called: sheep
// __construct with 2 params called: sheep,cat
// __construct with 3 params called: sheep,cat,dog
This is my take on it (build for php 5.6).
It will look at constructor parameter types (array, class name, no description) and compare the given arguments. Constructors must be given with least specificity last. With examples:
// demo class
class X {
public $X;
public function __construct($x) {
$this->X = $x;
}
public function __toString() {
return 'X'.$this->X;
}
}
// demo class
class Y {
public $Y;
public function __construct($y) {
$this->Y = $y;
}
public function __toString() {
return 'Y'.$this->Y;
}
}
// here be magic
abstract class MultipleConstructors {
function __construct() {
$__get_arguments = func_get_args();
$__number_of_arguments = func_num_args();
$__reflect = new ReflectionClass($this);
foreach($__reflect->getMethods() as $__reflectmethod) {
$__method_name = $__reflectmethod->getName();
if (substr($__method_name, 0, strlen('__construct')) === '__construct') {
$__parms = $__reflectmethod->getParameters();
if (count($__parms) == $__number_of_arguments) {
$__argsFit = true;
foreach ($__parms as $__argPos => $__param) {
$__paramClass= $__param->getClass();
$__argVar = func_get_arg($__argPos);
$__argVarType = gettype($__argVar);
$__paramIsArray = $__param->isArray() == true;
$__argVarIsArray = $__argVarType == 'array';
// parameter is array and argument isn't, or the other way around.
if (($__paramIsArray && !$__argVarIsArray) ||
(!$__paramIsArray && $__argVarIsArray)) {
$__argsFit = false;
continue;
}
// class check
if ((!is_null($__paramClass) && $__argVarType != 'object') ||
(is_null($__paramClass) && $__argVarType == 'object')){
$__argsFit = false;
continue;
}
if (!is_null($__paramClass) && $__argVarType == 'object') {
// class type check
$__paramClassName = "N/A";
if ($__paramClass)
$__paramClassName = $__paramClass->getName();
if ($__paramClassName != get_class($__argVar)) {
$__argsFit = false;
}
}
}
if ($__argsFit) {
call_user_func_array(array($this, $__method_name), $__get_arguments);
return;
}
}
}
}
throw new Exception("No matching constructors");
}
}
// how to use multiple constructors
class A extends MultipleConstructors {
public $value;
function __constructB(array $hey) {
$this->value = 'Array#'.count($hey).'<br/>';
}
function __construct1(X $first) {
$this->value = $first .'<br/>';
}
function __construct2(Y $second) {
$this->value = $second .'<br/>';
}
function __constructA($hey) {
$this->value = $hey.'<br/>';
}
function __toString() {
return $this->value;
}
}
$x = new X("foo");
$y = new Y("bar");
$aa = new A(array("one", "two", "three"));
echo $aa;
$ar = new A("baz");
echo $ar;
$ax = new A($x);
echo $ax;
$ay = new A($y);
echo $ay;
Result:
Array#3
baz
Xfoo
Ybar
Instead of the terminating exception if no constructor is found, it could be remove and allow for "empty" constructor. Or whatever you like.
Let me add my grain of sand here
I personally like adding a constructors as static functions that return an instance of the class (the object). The following code is an example:
class Person
{
private $name;
private $email;
public static function withName($name)
{
$person = new Person();
$person->name = $name;
return $person;
}
public static function withEmail($email)
{
$person = new Person();
$person->email = $email;
return $person;
}
}
Note that now you can create instance of the Person class like this:
$person1 = Person::withName('Example');
$person2 = Person::withEmail('yo#mi_email.com');
I took that code from:
http://alfonsojimenez.com/post/30377422731/multiple-constructors-in-php
Hmm, surprised I don't see this answer yet, suppose I'll throw my hat in the ring.
class Action {
const cancelable = 0;
const target = 1
const type = 2;
public $cancelable;
public $target;
public $type;
__construct( $opt = [] ){
$this->cancelable = isset($opt[cancelable]) ? $opt[cancelable] : true;
$this->target = isset($opt[target]) ? $opt[target] : NULL;
$this->type = isset($opt[type]) ? $opt[type] : 'action';
}
}
$myAction = new Action( [
Action::cancelable => false,
Action::type => 'spin',
.
.
.
]);
You can optionally separate the options into their own class, such as extending SplEnum.
abstract class ActionOpt extends SplEnum{
const cancelable = 0;
const target = 1
const type = 2;
}
Starting with PHP 8 we can use named arguments:
class Student {
protected int $id;
protected string $name;
public function __construct(int $id = null, string $name = null, array $row_from_database = null) {
if ($id !== null && $name !== null && $row_from_database === null) {
$this->id = $id;
$this->name = $name;
} elseif ($id === null && $name === null
&& $row_from_database !== null
&& array_keys($row_from_database) === [ 'id', 'name' ]
&& is_int($row_from_database['id'])
&& is_string($row_from_database['name'])) {
$this->id = $row_from_database['id'];
$this->name = $row_from_database['name'];
} else {
throw new InvalidArgumentException('Invalid arguments');
}
}
}
$student1 = new Student(id: 3, name: 'abc');
$student2 = new Student(row_from_database: [ 'id' => 4, 'name' => 'def' ]);
With proper checking it is possible to rule out invalid combinations of arguments, so that the created instance is a valid one at the end of the constructor (but errors will only be detected at runtime).
For php7, I compare parameters type as well, you can have two constructors with same number of parameters but different type.
trait GenericConstructorOverloadTrait
{
/**
* #var array Constructors metadata
*/
private static $constructorsCache;
/**
* Generic constructor
* GenericConstructorOverloadTrait constructor.
*/
public function __construct()
{
$params = func_get_args();
$numParams = func_num_args();
$finish = false;
if(!self::$constructorsCache){
$class = new \ReflectionClass($this);
$constructors = array_filter($class->getMethods(),
function (\ReflectionMethod $method) {
return preg_match("/\_\_construct[0-9]+/",$method->getName());
});
self::$constructorsCache = $constructors;
}
else{
$constructors = self::$constructorsCache;
}
foreach($constructors as $constructor){
$reflectionParams = $constructor->getParameters();
if(count($reflectionParams) != $numParams){
continue;
}
$matched = true;
for($i=0; $i< $numParams; $i++){
if($reflectionParams[$i]->hasType()){
$type = $reflectionParams[$i]->getType()->__toString();
}
if(
!(
!$reflectionParams[$i]->hasType() ||
($reflectionParams[$i]->hasType() &&
is_object($params[$i]) &&
$params[$i] instanceof $type) ||
($reflectionParams[$i]->hasType() &&
$reflectionParams[$i]->getType()->__toString() ==
gettype($params[$i]))
)
) {
$matched = false;
break;
}
}
if($matched){
call_user_func_array(array($this,$constructor->getName()),
$params);
$finish = true;
break;
}
}
unset($constructor);
if(!$finish){
throw new \InvalidArgumentException("Cannot match construct by params");
}
}
}
To use it:
class MultiConstructorClass{
use GenericConstructorOverloadTrait;
private $param1;
private $param2;
private $param3;
public function __construct1($param1, array $param2)
{
$this->param1 = $param1;
$this->param2 = $param2;
}
public function __construct2($param1, array $param2, \DateTime $param3)
{
$this->__construct1($param1, $param2);
$this->param3 = $param3;
}
/**
* #return \DateTime
*/
public function getParam3()
{
return $this->param3;
}
/**
* #return array
*/
public function getParam2()
{
return $this->param2;
}
/**
* #return mixed
*/
public function getParam1()
{
return $this->param1;
}
}
More modern aproach:
You are mixing seperate classes into one, entity & data hydration.
So for your case you should have 2 classes:
class Student
{
protected $id;
protected $name;
// etc.
}
class StudentHydrator
{
public function hydrate(Student $student, array $data){
$student->setId($data['id']);
if(isset($data['name')){
$student->setName($data['name']);
}
// etc. Can be replaced with foreach
return $student;
}
}
//usage
$hydrator = new StudentHydrator();
$student = $hydrator->hydrate(new Student(), ['id'=>4]);
$student2 = $hydrator->hydrate(new Student(), $rowFromDB);
Also please note that you should use doctrine or other ORM that already provides automatic entity hydration.
And you should use dependency injection in order to skip mannualy creating objects like StudentHydrator.
Kris's answer is great, but as Buttle Butku commented, new static() would be preferred in PHP 5.3+.
So I'd do it like this (modified from Kris's answer):
<?php
class Student
{
public function __construct() {
// allocate your stuff
}
public static function withID( $id ) {
$instance = new static();
$instance->loadByID( $id );
return $instance;
}
public static function withRow( array $row ) {
$instance = new static();
$instance->fill( $row );
return $instance;
}
protected function loadByID( $id ) {
// do query
$row = my_awesome_db_access_stuff( $id );
$this->fill( $row );
}
protected function fill( array $row ) {
// fill all properties from array
}
}
?>
Usage:
<?php
$student1 = Student::withID($id);
$student2 = Student::withRow($row);
?>
I also found an useful example in php.net OOP document.
In response to the best answer by Kris (which amazingly helped design my own class btw), here is a modified version for those that might find it useful. Includes methods for selecting from any column and dumping object data from array. Cheers!
public function __construct() {
$this -> id = 0;
//...
}
public static function Exists($id) {
if (!$id) return false;
$id = (int)$id;
if ($id <= 0) return false;
$mysqli = Mysql::Connect();
if (mysqli_num_rows(mysqli_query($mysqli, "SELECT id FROM users WHERE id = " . $id)) == 1) return true;
return false;
}
public static function FromId($id) {
$u = new self();
if (!$u -> FillFromColumn("id", $id)) return false;
return $u;
}
public static function FromColumn($column, $value) {
$u = new self();
if (!$u -> FillFromColumn($column, $value)) return false;
return $u;
}
public static function FromArray($row = array()) {
if (!is_array($row) || $row == array()) return false;
$u = new self();
$u -> FillFromArray($row);
return $u;
}
protected function FillFromColumn($column, $value) {
$mysqli = Mysql::Connect();
//Assuming we're only allowed to specified EXISTENT columns
$result = mysqli_query($mysqli, "SELECT * FROM users WHERE " . $column . " = '" . $value . "'");
$count = mysqli_num_rows($result);
if ($count == 0) return false;
$row = mysqli_fetch_assoc($result);
$this -> FillFromArray($row);
}
protected function FillFromArray(array $row) {
foreach($row as $i => $v) {
if (isset($this -> $i)) {
$this -> $i = $v;
}
}
}
public function ToArray() {
$m = array();
foreach ($this as $i => $v) {
$m[$i] = $v;
}
return $m;
}
public function Dump() {
print_r("<PRE>");
print_r($this -> ToArray());
print_r("</PRE>");
}
Call constructors by data type:
class A
{
function __construct($argument)
{
$type = gettype($argument);
if($type == 'unknown type')
{
// type unknown
}
$this->{'__construct_'.$type}($argument);
}
function __construct_boolean($argument)
{
// do something
}
function __construct_integer($argument)
{
// do something
}
function __construct_double($argument)
{
// do something
}
function __construct_string($argument)
{
// do something
}
function __construct_array($argument)
{
// do something
}
function __construct_object($argument)
{
// do something
}
function __construct_resource($argument)
{
// do something
}
// other functions
}
You could always add an extra parameter to the constructor called something like mode and then perform a switch statement on it...
class myClass
{
var $error ;
function __construct ( $data, $mode )
{
$this->error = false
switch ( $mode )
{
'id' : processId ( $data ) ; break ;
'row' : processRow ( $data ); break ;
default : $this->error = true ; break ;
}
}
function processId ( $data ) { /* code */ }
function processRow ( $data ) { /* code */ }
}
$a = new myClass ( $data, 'id' ) ;
$b = new myClass ( $data, 'row' ) ;
$c = new myClass ( $data, 'something' ) ;
if ( $a->error )
exit ( 'invalid mode' ) ;
if ( $b->error )
exit ('invalid mode' ) ;
if ( $c->error )
exit ('invalid mode' ) ;
Also with that method at any time if you wanted to add more functionality you can just add another case to the switch statement, and you can also check to make sure someone has sent the right thing through - in the above example all the data is ok except for C as that is set to "something" and so the error flag in the class is set and control is returned back to the main program for it to decide what to do next (in the example I just told it to exit with an error message "invalid mode" - but alternatively you could loop it back round until valid data is found).
I created this method to let use it not only on constructors but in methods:
My constructor:
function __construct() {
$paramsNumber=func_num_args();
if($paramsNumber==0){
//do something
}else{
$this->overload('__construct',func_get_args());
}
}
My doSomething method:
public function doSomething() {
$paramsNumber=func_num_args();
if($paramsNumber==0){
//do something
}else{
$this->overload('doSomething',func_get_args());
}
}
Both works with this simple method:
public function overloadMethod($methodName,$params){
$paramsNumber=sizeof($params);
//methodName1(), methodName2()...
$methodNameNumber =$methodName.$paramsNumber;
if (method_exists($this,$methodNameNumber)) {
call_user_func_array(array($this,$methodNameNumber),$params);
}
}
So you can declare
__construct1($arg1), __construct2($arg1,$arg2)...
or
methodName1($arg1), methodName2($arg1,$arg2)...
and so on :)
And when using:
$myObject = new MyClass($arg1, $arg2,..., $argN);
it will call __constructN, where you defined N args
then
$myObject -> doSomething($arg1, $arg2,..., $argM)
it will call doSomethingM, , where you defined M args;