如何通过Datamapper将受保护的属性保存到数据库?

时间:2018-09-28 19:50:16

标签: php oop

我正在尝试结合保护属性来掌握数据映射器的概念(我希望这是正确的术语)。

我正在构建认证系统。那里有一个User类

class User {

protected $id;
public $first_name;
public $mail;
protected $password;

如您所见,我选择将$ id和$ password设置为受保护的。其实我不太确定这是否正确,但我确实读过,应该尽量保持属性范围封闭。

我还构建了一个数据映射器,将用户对象保存到数据库中。通过构造函数依赖项注入将映射器注入到用户类。我这样从用户类内部调用映射器的保存方法

public function save () {
    return $this->dep['mapper']->saveUser($this);
}

在我的映射器saveUser()方法内部,我正在构建一个值数组以传递给数据库类。

public function saveUser($obj) {

    $insert_array;

    foreach ( $obj as $key => $value ) {
        $insert_array[$key] = $obj->get($key);
    }

这不能按预期的方式工作,因为我的映射器无法迭代受保护的属性。因此,这些属性不会传递给数据库。如果上述属性是公开的,那就很好了。

所以我的问题是:我如何设置我的类和方法,以便我的映射器能够在不暴露我所有属性的情况下获取所需的所有值?

额外:我已经使用__get()规避了这个问题,但是那是一种好的编码习惯吗?

1 个答案:

答案 0 :(得分:1)

对此没有唯一正确的答案,但是IMO不想让数据对象中的字段具有不同的可见性。这里有一些想法。

如果您对用户类的字段设置了不同的可见性,则可以进行如下更改以允许Mapper使用在用户类的save方法中构建的数组保存数据。

<?php

    class User
    {
        protected $id;
        public    $first_name;
        public    $mail;
        protected $password;

        private $dep = [];

        public function __construct()
        {
            $this->dep['mapper'] = new Mapper();
        }

        public function save()
        {
            $data = [
                'id' => $this->id,
                'first_name' => $this->first_name,
                'mail' => $this->mail,
                'password' => $this->password
            ];

            return $this->dep['mapper']->saveUser($data);
        }
    }

    class Mapper
    {
        public function saveUser($data)
        {
            foreach($data as $field=>$value)
            {
                echo $field.': '.$value.PHP_EOL;
            }
        }
    }

    $myUser = new User();

    $myUser->first_name = 'Lando';
    $myUser->mail = 'lando@cloudcity.gov';

    $myUser->save();

一个更正式的选择是使用数据传输对象(DTO),这是一个非常简单的类,仅封装数据。然后,您可以控制对业务对象中字段的访问。

<?php

class User
{
    private $dto;
    private $dep = [];

    public function __construct(UserDto $dto)
    {
        $this->dto           = $dto;
        $this->dep['mapper'] = new Mapper();
    }

    public function __get($propName)
    {
        if($propName=='password')
        {
            throw new Exception('No password for you');
        }
        elseif(property_exists($this->dto, $propName))
        {
            return $this->dto->$propName;
        }

        throw new InvalidArgumentException('No property '.$propName.' found in object');
    }

    public function __set($propName, $value)
    {
        if($propName=='id')
        {
            throw new Exception('ID may not be changed');
        }
        elseif($propName=='password')
        {
            throw new Exception('Password may not be changed');
        }
        elseif(property_exists($this->dto, $propName))
        {
            $this->dto->$propName = $value;
        }
        else
        {
            $this->$propName = $value;
        }
    }

    public function __isset($propName)
    {
        return (property_exists($this->dto, $propName));
    }

    public function save()
    {
        return $this->dep['mapper']->saveUser($this->dto);
    }
}

class UserDto
{
    public $id;
    public $first_name;
    public $mail;
    public $password;
}

class Mapper
{
    public function saveUser(UserDto $dto)
    {
        foreach ($dto as $key => $value)
        {
            $insert_array[$key] = $dto->$key;

            echo $key.': '.$value.PHP_EOL;
        }
    }
}

try
{
    $dto    = new UserDto();
    $myUser = new User($dto);

    $myUser->first_name = 'Lando';
    $myUser->mail       = 'lando@cloudcity.gov';

    echo $myUser->password;
    $myUser->password = 'foobar';

    $myUser->save();
}
catch(Exception $e)
{
    echo $e->getMessage().PHP_EOL;
}

使用get / set / has方法可以更好地控制对属性的访问。这很冗长,但具有在获取和设置数据时向数据添加逻辑或转换的好处。这种方法的主要优点之一是功能齐全的代码编辑器将对所有这些getter和setter进行代码完成,而魔术方法则无法实现。您当然可以将其与DTO结合使用。

<?php

class User
{
    private $data = [
        'id'=>null,
        'first_name'=>null,
        'mail'=>null,
        'password'=>null
    ];

    private $dep = [];

    public function __construct($data)
    {
        $validData = array_intersect_key($data, $this->data);

        foreach($validData as $currKey=>$currValue)
        {
            $this->data[$currKey] = $currValue;
        }

        $this->dep['mapper'] = new Mapper();
    }

    public function getId()
    {
        return $this->data['id'];
    }

    //Notice there is no setter for ID!

    public function hasId()
    {
        return (!empty($this->data['id']));
    }

    public function getFirstName()
    {
        return $this->data['first_name'];
    }

    public function setFirstName($val)
    {
        $this->data['first_name'] = $val;
    }

    public function hasFirstName()
    {
        return (!empty($this->data['first_name']));
    }

    public function getMail()
    {
        return $this->data['mail'];
    }

    public function setMail($val)
    {
        $this->data['mail'] = $val;
    }

    public function hasMail()
    {
        return (!empty($this->data['mail']));
    }

    //Notice there is no getter for ID!

    public function setPassword($val)
    {
        $hashed = md5($val); //Just an example, don't do this
        $this->data['password'] = $hashed;
    }

    public function hasPassword()
    {
        return (!empty($this->data['password']));
    }

    public function save()
    {
        return $this->dep['mapper']->saveUser($this->data);
    }
}

class Mapper
{
    public function saveUser($data)
    {
        foreach($data as $field=>$value)
        {
            echo $field.': '.$value.PHP_EOL;
        }
    }
}

try
{
    $dataFromDb = [
        'id'=>123,
        'first_name'=>'Lando',
        'mail'=>'lando@cloudcity.gov',
    ];

    $myUser = new User($dataFromDb);

    $myUser->setFirstName('Chewie');
    $myUser->setMail('wookie@kashyyyk.net');

    if(!$myUser->hasPassword())
    {
        $myUser->setPassword('AAAAAARRRRRRGHHHH');
    }

    $myUser->save();
}
catch(Exception $e)
{
    echo $e->getMessage().PHP_EOL;
}

我喜欢做这样的事情,其中​​所有冗长的样板都降级为封装数据并处理加载和保存单个记录的数据访问对象,并且单个记录的应用程序逻辑包含在主业务对象中。它们可以是超类或特质,无论您的船是什么。就个人而言,我有基于数据库架构为我编写所有DAO和业务对象类的代码,因此我只需要担心应用逻辑。

<?php

trait UserDao
{
    private $data = [
        'id'=>null,
        'first_name'=>null,
        'mail'=>null,
        'password'=>null
    ];

    private $deps;

    public function getId()
    {
        return $this->data['id'];
    }

    //Notice there is no setter for ID!

    public function hasId()
    {
        return (!empty($this->data['id']));
    }

    public function getFirstName()
    {
        return $this->data['first_name'];
    }

    public function setFirstName($val)
    {
        $this->data['first_name'] = $val;
    }

    public function hasFirstName()
    {
        return (!empty($this->data['first_name']));
    }

    public function getMail()
    {
        return $this->data['mail'];
    }

    public function setMail($val)
    {
        $this->data['mail'] = $val;
    }

    public function hasMail()
    {
        return (!empty($this->data['mail']));
    }

    private function _getPassword()
    {
        return $this->data['password'];
    }

    private function _setPassword($val)
    {
        $this->data['password'] = $val;
    }

    public function hasPassword()
    {
        return (!empty($this->data['password']));
    }

    public function load($data)
    {
        $validData = array_intersect_key($data, $this->data);

        foreach($validData as $currKey=>$currValue)
        {
            $this->data[$currKey] = $currValue;
        }
    }

    private function _save()
    {
        return $this->dep['mapper']->saveUser($this->data);
    }
}

class User
{
    use UserDao;

    public function __construct()
    {
        $this->dep['mapper'] = new Mapper();
    }

    public function setPassword($val)
    {
        $hashed = str_rot13($val); //Just an example, don't do this
        $this->_setPassword($hashed);
    }

    public function getPassword()
    {
        return str_rot13($this->_getPassword()); //Just an example, don't do this
    }

    public function save()
    {
        echo 'Do some complex validation here...'.PHP_EOL;

        $this->_save();
    }
}

class Mapper
{
    public function saveUser($data)
    {
        foreach($data as $field=>$value)
        {
            echo $field.': '.$value.PHP_EOL;
        }
    }
}

try
{
    $dataFromDb = [
        'id'=>123,
        'first_name'=>'Lando',
        'mail'=>'lando@cloudcity.gov',
    ];

    $myUser = new User();
    $myUser->load($dataFromDb);

    $myUser->setFirstName('Chewie');
    $myUser->setMail('wookie@kashyyyk.net');

    if(!$myUser->hasPassword())
    {
        $myUser->setPassword('AAAAAARRRRRRGHHHH');
    }

    $myUser->save();

    echo 'Unfutzed Password: '.$myUser->getPassword().PHP_EOL;
}
catch(Exception $e)
{
    echo $e->getMessage().PHP_EOL;
}

我建议对此主题进行一些研究,有很多模式,每个人都有不同的看法。