您不能在 PHP 类中放置两个具有唯一参数签名的 __construct 函数。我想这样做:
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.
}
}
在 PHP 中执行此操作的最佳方法是什么?
我可能会做这样的事情:
<?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
}
}
?>
然后,如果我想要一个我知道 ID 的学生:
$student = Student::withID( $id );
或者,如果我有一个 db 行数组:
$student = Student::withRow( $row );
从技术上讲,您并没有构建多个构造函数,只是静态帮助方法,但是您可以通过这种方式避免构造函数中的大量意大利面条代码。
Kris 的解决方案非常好,但我更喜欢工厂和流利的风格混合:
<?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);
?>
setLastName
(或更确切地说是所有 setter)来返回 $this
,而不是在同一属性上有效地使用两个 setter。
$student = new Student()->setFirstName("John")->setLastName("Doe");
create()
方法中使用 return new self()
来清洁
PHP 是一种动态语言,所以不能重载方法。您必须像这样检查参数的类型:
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.
}
}
public function __construct() {
$parameters = func_get_args();
...
}
$o = new MyClass('One', 'Two', 3);
现在 $paramters 将是一个值 'One'、'Two'、3 的数组。
编辑,
我可以补充
func_num_args()
将为您提供函数的参数数量。
正如这里已经展示的那样,在 PHP 中声明 multiple
构造函数的方法有很多,但它们都不是 correct
这样做的方式(因为 PHP 在技术上不允许这样做)。但这并不能阻止我们破解这个功能......这是另一个例子:
<?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.');
来源: The easiest way to use and understand multiple constructors:
希望这可以帮助。 :)
...
运算符一起使用,会更加优雅。
__construct($id)
和 __construct($row_from_database)
。两者都有一个参数,可能是第一个为 int,第二个为 array 或 object。当然,数字的附加可以扩展为某种 C++ 风格的参数签名(即 __construct_i($intArg)
和 __construct_a($arrayArg)
)。
你可以这样做:
public function __construct($param)
{
if(is_int($param)) {
$this->id = $param;
} elseif(is_object($param)) {
// do something else
}
}
自 5.4 版起,PHP 支持 traits。这不是正是您正在寻找的,但基于简单特征的方法将是:
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); }
}
我们最终得到了两个类,一个用于每个构造函数,这有点适得其反。为了保持理智,我将投入一个工厂:
class StudentFactory {
static public function getStudent($id, $name = null) {
return
is_null($name)
? new Student1($id)
: new Student2($id, $name)
}
}
所以,这一切都归结为:
$student1 = StudentFactory::getStudent(1);
$student2 = StudentFactory::getStudent(1, "yannis");
这是一种非常冗长的方法,但它可以非常方便。
这是一种优雅的方法。在给定参数数量的情况下,创建将启用多个构造函数的特征。您只需将参数数量添加到函数名称“__construct”。所以一个参数将是“__construct1”,两个“__construct2”......等等。
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
另一种选择是像这样在构造函数中使用默认参数
class Student {
private $id;
private $name;
//...
public function __construct($id, $row=array()) {
$this->id = $id;
foreach($row as $key => $value) $this->$key = $value;
}
}
这意味着您需要使用如下所示的行进行实例化:$student = new Student($row['id'], $row)
但要保持构造函数整洁。
另一方面,如果你想利用多态性,那么你可以像这样创建两个类:
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));
}
}
extends
其他类应该扩展的类,这意味着它们应该添加功能,这就是 OOP 的重点,而不是这个。
如其他评论中所述,由于 php 不支持重载,通常避免构造函数中的“类型检查技巧”,而是使用工厂模式
IE。
$myObj = MyClass::factory('fromInteger', $params);
$myObj = MyClass::factory('fromRow', $params);
您可以执行以下操作,这非常简单且非常干净:
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;
}
}
func_get_args
分配给一个变量并在下一行再次调用它?如果您在决定需要基于 fund_num_args
后仅调用 func_get_args
,也会更好。
这个问题已经用非常聪明的方法来满足要求,但我想知道为什么不退后一步,问一个基本问题,为什么我们需要一个有两个构造函数的类?如果我的类需要两个构造函数,那么我设计类的方式可能需要更多考虑来提出更清洁和更可测试的设计。
我们试图将如何实例化一个类与实际的类逻辑混为一谈。
如果 Student 对象处于有效状态,那么它是从 DB 行还是从 Web 表单或 cli 请求中构造的数据是否重要?
现在回答这里可能出现的问题,如果我们不添加从 db 行创建对象的逻辑,那么我们如何从 db 数据创建对象,我们可以简单地添加另一个类,将其称为 StudentMapper if您对数据映射器模式感到满意,在某些情况下您可以使用 StudentRepository,如果没有适合您的需求,您可以创建 StudentFactory 来处理各种对象构建任务。
底线是在我们处理域对象时将持久层放在我们的脑海中。
我知道我在这里参加聚会已经很晚了,但我想出了一个相当灵活的模式,它应该允许一些非常有趣和通用的实现。
像往常一样设置你的班级,使用你喜欢的任何变量。
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;
}
}
}
}
}
当您制作对象时,只需传递一个关联数组,该数组的键与您的变量名称相同,就像这样......
$sample_variable = new MyClass([
'myVar2'=>123,
'i_dont_want_this_one'=> 'This won\'t make it into the class'
]);
print_r($sample_variable);
此实例化后的 print_r($sample_variable);
产生以下结果:
MyClass Object ( [myVar1:protected] => [myVar2:protected] => 123 )
因为我们在 __construct(...)
中将 $group
初始化为 null,所以也可以不向构造函数传递任何内容,就像这样......
$sample_variable = new MyClass();
print_r($sample_variable);
现在输出完全符合预期:
MyClass Object ( [myVar1:protected] => [myVar2:protected] => )
我写这个的原因是我可以直接将 json_decode(...)
的输出传递给我的构造函数,而不必太担心它。
这是在 PHP 7.1 中执行的。享受!
我在创建具有不同签名的多个构造函数时遇到了同样的问题,但不幸的是,PHP 没有提供这样做的直接方法。但是,我找到了一个技巧来克服这个问题。希望也适用于你们所有人。
<?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
让我在这里添加我的沙粒
我个人喜欢将构造函数添加为返回类(对象)实例的静态函数。以下代码是一个示例:
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;
}
}
请注意,现在您可以像这样创建 Person 类的实例:
$person1 = Person::withName('Example');
$person2 = Person::withEmail('yo@mi_email.com');
我从以下位置获取代码:
http://alfonsojimenez.com/post/30377422731/multiple-constructors-in-php
嗯,很惊讶我还没有看到这个答案,假设我会把我的帽子扔进戒指。
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',
.
.
.
]);
您可以选择将选项分离到它们自己的类中,例如扩展 SplEnum。
abstract class ActionOpt extends SplEnum{
const cancelable = 0;
const target = 1
const type = 2;
}
这是我的看法(为 php 5.6 构建)。
它将查看构造函数参数类型(数组、类名、无描述)并比较给定的参数。构造函数必须最后给出最不具体的。举个例子:
// 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;
结果:
Array#3
baz
Xfoo
Ybar
如果没有找到构造函数,则不是终止异常,而是可以将其删除并允许“空”构造函数。或者任何你喜欢的。
对于 php7,我也比较了参数类型,你可以有两个参数数量相同但类型不同的构造函数。
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");
}
}
}
要使用它:
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;
}
}
MultiConstructorClass
的两个实例?谢谢。
更现代的方法:您将单独的类混合为一个,实体和数据水合。因此,对于您的情况,您应该有 2 个类:
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);
另请注意,您应该使用已经提供自动实体水合的学说或其他 ORM。你应该使用依赖注入来跳过手动创建像 StudentHydrator 这样的对象。
从 PHP 8 开始,我们可以使用命名参数:
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' ]);
通过适当的检查,可以排除无效的参数组合,以便创建的实例在构造函数的末尾是一个有效的实例(但只会在运行时检测到错误)。
为了回应 Kris 的最佳答案(顺便说一句,它帮助我设计了自己的课程),这里有一个修改版本,供那些可能觉得它有用的人使用。包括从任何列中选择和从数组中转储对象数据的方法。干杯!
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>");
}
按数据类型调用构造函数:
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
}
您总是可以向构造函数添加一个额外的参数,称为 mode ,然后对其执行 switch 语句......
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' ) ;
如果您想添加更多功能,也可以随时使用该方法,您可以在 switch 语句中添加另一个案例,您还可以检查以确保有人发送了正确的内容 - 在上面的示例中,所有数据都正常C 除外,因为它设置为“某事”,因此设置了类中的错误标志,并将控制权返回给主程序以决定下一步该做什么(在示例中,我只是告诉它退出错误消息“无效模式” - 但或者您可以将其循环回来,直到找到有效数据)。
我创建了这个方法,不仅可以在构造函数中使用它,还可以在方法中使用它:
我的构造函数:
function __construct() {
$paramsNumber=func_num_args();
if($paramsNumber==0){
//do something
}else{
$this->overload('__construct',func_get_args());
}
}
我的 doSomething 方法:
public function doSomething() {
$paramsNumber=func_num_args();
if($paramsNumber==0){
//do something
}else{
$this->overload('doSomething',func_get_args());
}
}
两者都使用这种简单的方法:
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);
}
}
所以你可以声明
__construct1($arg1), __construct2($arg1,$arg2)...
或者
methodName1($arg1), methodName2($arg1,$arg2)...
等等 :)
使用时:
$myObject = new MyClass($arg1, $arg2,..., $argN);
它将调用 __constructN
,您在其中定义了 N
args
然后 $myObject -> doSomething($arg1, $arg2,..., $argM)
它将调用 doSomethingM
, ,您在其中定义了 M
args;
Kris's answer 很棒,但作为 Buttle Butku commented,new static()
在 PHP 5.3+ 中会更受欢迎。
所以我会这样做(根据克里斯的回答修改):
<?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
}
}
?>
用法:
<?php
$student1 = Student::withID($id);
$student2 = Student::withRow($row);
?>
我还在 php.net OOP 文档中找到了 an useful example。
不定期副业成功案例分享
__construct()
设为私有,以防止有人偶尔分配“未初始化”实例吗?new static()
而不是new self()
的注意事项,因为new static()
在子类中会更有效地工作。