PHP - 将字符串值分配给类型提示为枚举的变量

时间:2021-07-25 20:24:09

标签: php

我有一些用于应用程序的 PHP 片段,我试图限制来自 JavaScript 应用程序前端请求的输入。该页面使用 JSON 对象发送请求,该对象包含我指定为“打开”、“完成”或“关闭”的字段值。我想防止不必要的输入篡改或发送值。

问题:

下面的属性 $eventstatus 是用枚举类型提示的,但是当我在 $array['EventStatus'] 中分配字符串值时,PHP (7.4.9) 报告了我的类型不兼容的错误。它需要查看 Status 类型,而实际上我正在为其分配一个字符串。
我该如何解决这个问题?

$event->eventstatus = $array['EventStatus'];   

枚举类(状态)

<?php
    namespace app\enums;

    abstract class Status
    {
        const Open = 'Open';
        const Complete = 'Complete';
        const Closed = 'Closed';
    }

Mapper 类成员函数 - 片段,下面的代码采用数组值并将其映射到类属性

<?php
    function mapFromArray($event, $array) {
        if (!is_null($array['EventStatus'])) $event->eventstatus = $array['EventStatus'];   
    }

模型类

<?php
    namespace data\model;
    use app\enums\Status;

    class Event
    {
        public $eventid;  
        public $riskid;        
        public $eventtitle;
        public Status $eventstatus;
    }

2 个答案:

答案 0 :(得分:1)

您的类型提示实际上告诉 PHP 您希望 $eventstatusStatus实例。但这些值实际上只是简单的字符串:'Open''Complete''Closed'。 所以正确的类型提示是:

<?php
    namespace data\model;
    use app\enums\Status;

    class Event
    {
        // ...
        public string $eventstatus;
    }

但是有了这个 PHP 接受 any string 而不仅仅是一个“有效”的。在这里使用适当的枚举会有所帮助,但目前 PHP 7 没有对枚举的本机支持(即 implemented for PHP 8.1 though)。

如果您想使用 Status 类以获得更易读的代码,您只需将类型提示更改为 string

如果你想验证输入数据,你可以像这样扩展代码:

<?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
        }
    }
}

如果你想使用严格的类型提示来确保任何地方的有效性,你需要将值包装到类的实例中,例如:

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
        }
    }
}

答案 1 :(得分:0)

我尝试了一种与使用数组值建议的方法略有不同的方法,但仍然依赖某种数组来检查允许的值。

在我的 Events 类中,我从抽象类 Mapper 扩展而来(我在其中添加了一个新的 performMapping 函数以使映射更加动态)

<?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;               
        }
    }

模型 - 添加魔法方法 (__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;
            }
        }
    }

映射器

<?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;
        }

枚举

<?php
    namespace app\enums;

    abstract class Status
    {
        const Open = 'Open';
        const Complete = 'Complete';
        const Closed = 'Closed';    
    }