Let's say I do a
$response = Http::get('http://example.com');
$i_want_to_be_a_model = $response->json();
And I have a \App\Models\Example model.
How can I make the $i_want_to_be_a_model to be an Example model?
I want to do this, because I want to add a method statusText() to the model, that is not part of the result.
class Example extends Model {
// ..
public string $statusText;
public int $status;
public function statusText() {
switch ($this->status) {
case 100:
$this->statusText = "foo";
break;
//..
default:
$this->statusText = "bar";
}
}
}
If there is a more elegant way if doing this, please let me know.
You can define a helper function or a Factory class to create objects of Example class.
For eg:
<?php
namespace App\Factories;
use App\Models\Example;
use Illuminate\Support\Facades\Schema;
class ExampleFactory
{
public function __construct(array $attributes)
{
$example = new Example;
$fields = Schema::getColumnListing($example->getTable());
foreach($attributes as $field => $value) {
if(in_array($field, $fields) {
$example->{$field} = $value;
}
}
return $example;
}
public static function makeFromArray(array $attributes)
{
return new static(... $attributes);
}
}
Then you can use the Factory as
// use App\Factories\ExampleFactory;
$response = Http::get('http://example.com');
$example = ExampleFactory::makeFromArray(json_decode($response->json(), true));
//Now you can do whatever you want with the instance, even persist in database
$example->save();
Related
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;
}
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
}
}
}
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';
}
I am setting up a class called a DataMapper for an MVC framework that I am developing.
The DataMapper class will basically hold a bunch of Rules that map a data model structure to the database table/column structure - and it will list criteria for each Rule.
A simpler version of the Rule class would look like (keep in mind these code snippets are examples):
class DataMapRule {
// required rule properties
public $required = array("modelName", "columnName", "type");
// optional values
public $options = array("minVal", "nullOrempty", "maxCharLength");
public $finalRule = array();
// model property is the prop name in the model
// column name is
public function __construct($modelProperty = "", $columnName = "", $type = "") {
$this->finalRule["modelName"] = $modelProperty;
$this->finalRule["columnName"] = $columnName;
$this->finalRule["type"] = $type;
}
public function addCriteria($option, $val) {
if(in_array($option, $this->options)) {
$this->finalRule[$option] = $val;
}
}
}
And a simpler version of the extended DataMapper class would look like:
class UserDataMapper extends DataMapper {
public $map = array();
public function __construct() {
$rule = new DataMapRule("_age", "Age", "int");
$rule->addCriteria("minVal", "1");
$rule->addCriteria("nullOrEmpty", false);
$this->map[] = $rule->finalRule;
$rule = new DataMapRule("_firstName", "FirstName", "string");
$rule->addCriteria("maxCharLength", 50);
$rule->addCriteria("nullOrEmpty", false);
$this->map[] = $rule->finalRule;
}
}
And I am going to create these maps and rules for all my models/modules.
What I am trying to do is clean up the code / make it easier to code and follow. Is there any way to set up the class(es) so that in the DataMapper constructor I can do something like:
public function __construct() {
$this->map[] = new DataMapRule("_firstName", "FirstName", "string")
->addCriteria("maxCharLength", 50)
->addCriteria("nullOrEmpty", false)
->finalRule;
}
This is usually called a 'fluid' interface, and to implement this, all you really have to do is return $this from your addCriteria() function.
public function addCriteria($option, $val) {
if(in_array($option, $this->options)) {
$this->finalRule[$option] = $val;
}
return $this;
}
Returning $this is means that you can call addCriteria again on the result of addCriteria
I have a class, more specific a repository. This repository will hold my validators so I can reach them whenever I want. Currently it looks like this:
class ValidatorRepository {
private $validators;
public function __construct() {
$this->validators = array();
}
public function get($key) {
return $this->validators[$key];
}
public function add($key, iValidator $value) {
$this->validators[$key] = $value;
}
public static function getInstance() {
//(...)
}
}
And with this class I would like to do something like this:
$vr = ValidatorRepository::getInstance();
$vr->add("string", new StringValidator());
I can insert something else than a instantiated object if that is for the better.
.. and later on, somewhere else;
$vr = ValidatorRepository::getInstance();
$vr->get("string"); // should return a *new* instance of StringValidator.
The idea is that the ValidatorRepository should NOT know about the classes before these are added.This works fine, as long as I return the current object.
But instead I would like a new object of the class. I could to this by putting a static getInstance() function in each validator, or use eval in some way, but I hope there might be another, less ugly, way.
I believe you should be able to do something this simple:
public function add( $key, iValidator $value ) {
$this->validators[ $key ] = get_class( $value ); // this call can be moved to get() if you wish
}
public function get( $key ) {
return new $this->validators[ $key ];
}
get_class() takes namespaces into account, so if you use namespaces then it will still be fine.
A slightly more flexible approach might be this:
public function add( $key, iValidator $value ) {
$this->validators[ $key ] = $value;
}
public function get( $key, $new = true ) {
if ($new) {
$class = get_class( $this->validators[ $key ] );
$class = new $class;
} else {
$class = $this->validators[ $key ];
}
return $class;
}
What you should be using is instead either inheritance:
abstract class Validated {
public function validate(){
foreach(self::VALIDATIONS as $val) {
// ...
}
}
}
class Person extends Validated {
protected $name;
const VALIDATIONS = array(
'name' => array( 'length' => new LengthValidator(15) )
);
}
or traits:
trait Validated {
function validate(){
// ...
}
}
class Person {
use Validated;
}
Shoving all the validation logic into a single class violates the single responsibly principle since it becomes responsible for for validating all classes which use it. It will quickly get out of hand.
Note that I have used a constant for the validations - you rarely need to change validation rules for a class during runtime.
I'm using symfony and doctrine.
The server gets a HTTP PATCH request for the URL /company/{id} containing the property of a model and its value like {"name": "My new name"} The new value needs to be persisted into the DB.
$request = Request::createFromGlobals();
$requestContentJSON = $request->getContent();
$requestContentObj = json_decode($requestContentJSON);
$repository = $this->getDoctrine()->getRepository('MyBundle:Company');
$company = $repository->find($id);
Now I could just enter $company->setName($requestContentObj[0]); but the property being received will vary. Right now I'm using the following code to be able to handle every property:
foreach($requestContentObj as $key => $value){
switch($key){
case 'name':
$company->setName($value);
break;
case 'department':
$company->setDepartment($value);
break;
case 'origin':
$company->setOrigin($value);
break;
case 'headquarters':
$company->setHeadquarters($value);
break;
case 'email':
$company->setEmail($value);
break;
case 'twitterid':
$company->setTwitterId($value);
break;
case 'description':
$company->setDescription($value);
break;
}
}
But this doesn't look very smart especially because I know that I will have other entities like news, products, users, etc that will have their properties updated in the same manner. I'd like to do something like this:
$company->set("property", "value");
First thought that crossed my mind was to put this switch statement inside the company class inside this set function and also inside all the other entity classes I have. But is there a better way? Maybe symfony/doctrine has the solution already built-in, but I didn't find anything that would suit me.
I still want to use setters and getters as a long-term investment.
Thank you.
Assuming you'll have the property names similar to method names.
You can do something like this. To set multiple properties.
Class customer {
protected $_email;
public function __construct(array $config = array()){
$this->setOptions($config);
}
public function getEmail(){
return $this->_email;
}
public function setEmail($email){
$this->_email = $email;
}
public function setOptions(array $options)
{
$_classMethods = get_class_methods($this);
foreach ($options as $key => $value) {
$method = 'set' . ucfirst($key);
if (in_array($method, $_classMethods)) {
$this->$method($value);
} else {
throw new Exception('Invalid method name');
}
}
return $this;
}
public function setOption($key, $value){
return $this->setOptions(array($key, $value));
}
}
Now you can simply do this:
$array = array('email' => 'abc.#gmail.com');
$customer = new Customer($array);
echo $customer->getEmail();
My inital thought would be to add a merge method to your class, like so:
<?php
// example Company entity
class Company
{
private $name;
function setName($name)
{
$this->name = $name;
}
function getName()
{
return $this->name;
}
function merge(\stdClass $obj)
{
// get the object vars of the passed object
// iterate, and replace matching properties
foreach (get_object_vars($obj) as $prop => $val) {
if (property_exists($this, $prop)) {
$this->$prop = $val;
}
}
}
}
$company = new Company();
// mocking your request object
$requestContentObj = new stdClass();
$requestContentObj->name = 'acme';
$company->merge($requestContentObj);
var_dump($company);
Yields:
class Company#1 (1) {
private $name =>
string(4) "acme"
}
This silently dumps any passed values that do not match any properties in your Company class, which may or may not be what you want. Hope this helps :)
What I can propose is not using the setters, but it seems a good fit for your problem.
In doctrine 1.2.4, you could use DQL as follow:
$q = Doctrine_Core::getTable("myTable")->createQuery("q")
->update()
->where("id = ?", $id);
foreach($requestContentObj as $key => $value)
{
$q->set($key, "?", $value);
}
$q->execute();
We have a system where we have reason to instantiate an object before we know what its specific type is. For example, we wish to instantiate a class "media" before we know whether the final class will be "book" or "cd".
Here is what we are doing. We instantiate the "media" object, and once we know what type of media it is, we instantiate a "book", passing in the media object.
class Book extends Media
{
public function __construct( $parent )
{
$vars = get_object_vars( $parent );
foreach( $vars as $key => $value )
$this->$key = $value;
}
}
//elsewhere
$item = new Media();
$item->setPrice( $price );
//other code, figure out the item type
$item = new Book( $item );
Is there a better way to do something like this? Is this dynamic polymorphism?
In case u really can't determine what type of object is i can recommend u factory pattern. With this pattern u has only one entry point and this helps u to get yr code simpler and readable.
Short example:
class ObjectFactory {
public static function factory($object, $objectType)
{
$result = false;
switch ($objectType) {
case 'book':
$result = new Book;
$result->setParams($object->getParams());
break;
case 'otherType':
$result = new OtherType;
$result->setParams($object->getParams());
// or
$result->setParamsFromObject($object);
break;
... //etc
}
return $result;
}
}
class Book extends MediaAbstract
{
public function __set($name, $value)
{
$this->_params($name, $value);
}
public function __get($name)
{
return $this->_params[$value];
}
public function setParams(array $params)
{
$this->_params = $params;
return $this;
}
public function getParams()
{
return $this->_params;
}
// in case u want store params as properties
public function setParamsFromObject($object)
{
$vars = get_object_vars($object);
foreach ($vars as $key => $value) {
$this->$key = $value;
}
return $this;
}
}
$media = new Media;
$media->setParams($params)
// some stuff
//...
$book = ObjectFactory::factory($media, $objectType);