2013-08-21 44 views
10

这是一个哲学,但我想很多人遇到过这个问题。目标是访问PHP中的各种(动态声明的)属性,并在未设置时去除通知。如何解决PHP中缺少的对象属性?

为什么不到__get
如果您可以申报自己的班级,那么这是个不错的选择,但不适用于stdClass,SimpleXML或类似的情况。扩展它们不是和可选的,因为你通常不直接实例化这些类,它们是作为JSON/XML解析的结果返回的。

例子:

$data = '{"name": "Pavel", "job": "programmer"}'; 
$object = json_decode($data); 

我们简单stdClass对象。存在的问题是显而易见的:

$b = $data->birthday; 

属性没有定义,因此通知书提出:

PHP Notice: Undefined property: stdClass::$birthday 

,如果你认为你从解析一些JSON获取对象这经常发生。天真的解决方案是显而易见的:

$b = isset($data->birthday) ? $data->birthday : null; 

然而,人们很快就厌倦了包装每个访问者到这个。尤其是链接对象时,如$data->people[0]->birthday->year。检查是否设置了people。检查是否设置了第一个元素。检查是否设置了birthday。检查是否设置了year。我觉得有点过分...

问题: 最后,我的问题是在这里。
这个问题的最佳方法是什么?沉默通知似乎不是最好的主意。并检查每个属性是困难的。我看到一些解决方案,如Symfony property access,但我认为它仍然是太多的样板。有没有更简单的方法?无论是第三方库,PHP设置,C扩展,我都不在乎它的工作原理......还有什么可能的缺陷?

+2

*“但是,当将每个访问器都包含进来时尤其如此,特别是链接对象时,如$ data-> people [0] - > birthday-> year。”*理解。即使'$ data'为null,没有任何副作用,您也可以安全地执行'isset($ data-> people [0] - > birthday-> year)'。 – netcoder

+0

这是一个很好的观点,我没有意识到这一点。谢谢! (还有,有没有更简短的版本''$ y = isset($ x)?$ x:null;' –

+0

没有,如果你看其他语言,PHP实际上是最短的形式。 ,你必须手动检查每个子元素为null,在Python中,你必须用'try..except'块来包围它,如果你真的想缩短,你总是可以写一个小包装函数。 – netcoder

回答

3

如果我理解正确,您想要处理第三方对象,您无法控制,但是您的逻辑需要某些可能不存在于对象上的属性。这意味着,您接受的数据对您的逻辑无效(或应宣布无效)。然后检查有效性的负担将进入您的验证程序。我希望你已经有了处理第三方数据的最佳实践。 :)

您可以使用自己的验证器或框架。一个常用的方法是编写一组规则,数据需要遵守才能生效。

现在在您的验证程序中,每当规则不服从时,您将描述错误的异常以及附加包含您要使用的信息的异常属性。稍后当你在你的逻辑中的某个地方打电话给你的验证器时,你把它放在try {...}区块内,并将你的例外置于你的例外并处理它们,也就是写下你为这些例外保留的特殊逻辑。根据一般惯例,如果您的逻辑在块中变得太大,您希望将其“外包”为功能。 引用Robert Martin的“Clean Code”的优秀书籍,强烈推荐给任何开发者:

函数的第一条规则是它们应该很小。第二个是他们应该比那个小。

我理解你的无奈应对永恒isset S和看到这里的问题是,每次你需要写一个处理程序处理的这个是技术问题,或不存在该属性的原因。这个技术问题在抽象层次上是非常低的,为了正确处理这个问题,你必须一直沿着抽象链去达到一个对你的逻辑有意义的更高的步骤。在不同抽象层次之间跳跃总是很难,尤其是相距甚远。这也是使你的代码难以维护,并建议避免。 理想情况下,您的整个体系结构被设计为一个树,其中坐在其节点处的控制器只知道从它们下降的边缘。

例如,回到你的例子,问题是 -

  • Q - 什么是你的那$data->birthday丢失情况的应用程序是什么意思?

含义取决于抛出异常的当前函数想要达到的效果。这是处理您的例外的便利场所。

希望它能帮助:)

2

具有可选字段的数据格式很难处理。如果您有第三方访问或提供数据,这些问题尤其存在问题,因为很少有足够的文档来全面涵盖导致字段出现或消失的所有原因。当然,排列往往难以测试,因为编码人员不会本能地认识到这些字段可能在那里。

这是说,如果你能避免在您的数据可选字段很长的路要走,以应对在PHP中缺少对象属性最好的办法是不要有任何遗漏对象属性 ...

如果你正在处理的数据不取决于你,那么我会考虑强制所有字段的默认值,可能是通过助手函数或原型模式的某种疯狂变化。您可以构建一个数据模板,其中包含数据所有字段的默认值,并将其与实际数据合并。

但是,如果你这样做,你失败了,除非?(这是另一种编程理念,需要考虑。)我想可以提供一个安全的默认参数来满足任何缺失字段的数据验证。但特别是在处理第三方数据时,您应该针对任何正在使用默认值进行涂抹的领域进行高度的偏执狂。将它设置为空并且 - 在这个过程中 - 太难了解为什么它首先被丢失。

你也应该问你想达到什么目的?明晰?安全?稳定性?最小的代码重复?这些都是有效的目标。累了吗?更少。它暗示缺乏纪律,并且一个好程序员总是被训练。当然,我会接受的是,如果人们认为它是一件苦差事,那么人们不太可能做某件事。

我的意思是,您的问题的答案可能会有所不同,具体取决于为什么它被要求。零努力的解决方案可能无法使用,所以如果你只是将一个编程任务交给另一个编程任务,你是否正在解决任何问题?

如果你正在寻找一个系统化的解决方案,将保证数据总是在您指定的格式,从而减少了在处理这些数据,那么也许我上面建议的将是代码逻辑测试号的帮助。但它不会没有成本和努力。

+0

感谢你的支持。 。我也很乐意听到其他的方法。 –

1

最好的答案已经给出,但这里是一个懒惰的一个:

$data = '{"name": "Pavel", "job": "programmer"}'; 
$object = json_decode($data); 
if(
    //...check mandatory properties: !isset($object->...)&& 
    ){ 
    //error 
} 
error_reporting(E_ALL^E_NOTICE);//Yes you're right, not the best idea... 
$b = $object->birthday?:'0000-00-00';//thanks Elvis (php>5.3) 
//Notice that if your default value is "null", you can just do $b = $object->birthday; 
//assign other vars here 
error_reporting(E_ALL); 
//Your code 
3

一个解决方案(我不知道这是否是更好的解决方案,但一可能的解决方案)是制造这样的功能:

function from_obj(&$type,$default = "") { 
    return isset($type)? $type : $default; 
} 

然后

$data = '{"name": "Pavel", "job": "programmer"}'; 
$object = json_decode($data); 

$name = from_obj($object->name  , "unknown"); 
$job = from_obj($object->job  , "unknown"); 
$skill = from_obj($object->skills[0] , "unknown"); 
$skills = from_obj($object->skills , Array()); 

echo "Your name is $name. You are a $job and your main skill is $skill"; 

if(count($skills) > 0) { 
    echo "\n\nYour skills: " . implode(",",$skills); 
} 

我认为这是convienent因为你必须在你的脚本,你想要什么的顶部,它应该是(数组,字符串等)

编辑:

另一种解决方案。你可以创建一个扩展ArrayObject的桥梁类:

class ObjectBridge extends ArrayObject{ 
    private $obj; 
    public function __construct(&$obj) { 
     $this->obj = $obj; 
    } 

    public function __get($a) { 
     if(isset($this->obj->$a)) { 
      return $this->obj->$a; 
     }else { 
      // return an empty object in order to prevent errors with chain call 
      $tmp = new stdClass(); 
      return new ObjectBridge($tmp); 
     } 
    } 
    public function __set($key,$value) { 
     $this->obj->$key = $value; 
    } 
    public function __call($method,$args) { 
     call_user_func_array(Array($this->obj,$method),$args); 
    } 
    public function __toString() { 
     return ""; 
    } 
} 

$data = '{"name": "Pavel", "job": "programmer"}'; 
$object = json_decode($data); 

$bridge = new ObjectBridge($object); 

echo "My name is {$bridge->name}, I have " . count($bridge->skills). " skills and {$bridge->donald->duck->is->paperinik}<br/>"; 
// output: My name is Pavel, I have 0 skills and 
// (no notice, no warning) 

// we can set a property 
$bridge->skills = Array('php','javascript'); 

// output: My name is Pavel, my main skill is php 
echo "My name is {$bridge->name}, my main skill is {$bridge->skills[0]}<br/>"; 


// available also on original object 
echo $object->skills[0]; // output: php 

个人而言,我更喜欢第一个解决方案。它更清晰,更安全。

0
function check($temp=null) { 
if(isset($temp)) 
return $temp; 

else 
return null; 
} 

$b = check($data->birthday); 
+0

小心返回null - 它可以创建s IDE的影响,并建议避免由鲍勃叔叔。 –

+0

这将无法正常工作,因为在调用函数之前错误会上升。 –

1

使用代理对象 - 它会增加只是一个很小的类和每个对象实例化一个行中使用它。

class ProxyObj { 
    protected $obj; 
    public function __construct($obj) { 
     $this->_obj = $obj; 
    } 
    public function __get($key) { 
    if (isset($this->_obj->$key)) { 
     return $this->_obj->$key; 
    } 
    return null; 
    } 
    public function __set($key, $value) { 
     $this->_obj->$key = $value; 
    } 
} 

$proxy = new ProxyObj(json_decode($data)); 
$b = $proxy->birthday; 
+2

这里的问题是嵌套对象。 __get()应检查是否($ this - > _ obj - > $ key instanceof StdClass)'并返回'return new self($ this - > _ obj - > $ key);'如果为true。 – popthestack

+1

此外,如果'user'不存在,则返回'null'将导致'$ obj-> user-> birthday'失败。然而,你不能在它的地方返回一个'ProxyObj',因为'$ obj-> user-> birthdayBlah'会返回一个对象。 '__toString'可以在这里帮助,但不会完全解决问题。 – popthestack

0

我已经打了这个问题,主要是从一个NoSQL的支持的API,通过设计有不一致的结构,例如获取JSON数据如果用户有一个地址,你会得到$用户>地址,否则地址关键只是不存在。而不是把吨issets在我的模板,我写了这个类...

class GracefulData 
{ 
    private $_path; 
    public function __construct($d=null,$p='') 
    { 
     $this->_path=$p; 
     if($d){ 
      foreach(get_object_vars($d) as $property => $value) { 
       if(is_object($d->$property)){ 
        $this->$property = new GracefulData($d->$property,$this->_path . '->' . $property); 
       }else{ 
        $this->$property = $value; 
       } 
      } 
     } 
    } 
    public function __get($property) { 
     return new GracefulData(null,$this->_path . '->' . $property); 
    } 
    public function __toString() { 
     Log::info('GracefulData: Invalid property accessed' . $this->_path); 
     return ''; 
    } 
} 

然后实例它像这样

$user = new GracefulData($response->body); 

它会很好地处理现有的和非现有属性嵌套调用。如果你访问现有非对象财产如的孩子

$用户> firstName->东西

0

可以JSON对象进行解码到一个数组中什么也不能,虽然处理是:

$data = '{"name": "Pavel", "job": "programmer"}'; 
$jsonarray = json_decode($data, true); 
$b = $jsonarray["birthday"]; // NULL 
相关问题