1

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;
    }
4
  • What is $array['EventStatus'] ? Commented Jul 25, 2021 at 20:29
  • Presently I have assigned it a string value either 'Open', 'Complete', or 'Closed' based on completion of values in input fields and a clicking on a submit button. $array contains the JSON values from the front end application. My objective is to prevent invalid string values from being sent to the server. Commented Jul 25, 2021 at 20:31
  • Does this answer your question? Enumerations on PHP Commented Jul 25, 2021 at 20:54
  • Why not using a simple array that holds the valid values and use in_array() ? A Status is not a string Commented Jul 25, 2021 at 20:56

2 Answers 2

1

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
        }
    }
}
Sign up to request clarification or add additional context in comments.

Comments

0

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

1 Comment

Unclear if making $eventstatus private will prevent intellisense from showing the member variable with magic methods. That is something I need to research. If I have introduced an unnecessary problem with a private property like $eventstatus, I would be open to a solution to avoid intellisense issues.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.