What is the meaning of `$this->{$this}` in php? - php

I have encountered that in Authenticatable trait in laravel:
public function setRememberToken($value)
{
if (! empty($this->getRememberTokenName())) {
$this->{$this->getRememberTokenName()} = $value; //here
}
}
public function getRememberTokenName()
{
return $this->rememberTokenName;
}
I know the first $this will point out the class (Model) that the trait is used in. However,
What is the meaning of the second {$this}?
What does it actually do?
Why they did not simply say:
public function setRememberToken($value)
{
if (! empty($this->getRememberTokenName())) {
$this->rememberTokenName = $value;
}
}

This is complex curly syntax.
Basically if $this->getRememberTokenName() returns the string value six then the expression is essentially $this->six

The trait is using the value returned from getRememberTokenName(), which is really just the value of $this->rememberTokenName, as the name of the property in the model class that should hold the value. For instance, if $this->rememberTokenName is set to 'myToken', the setRememberTokenFunction is doing the equivalent of
$this->myToken = $value;
This is just a convoluted way of allowing the model class to configure the name of the variable that holds the token value.
<?php
trait Authenticatable
{
public function setRememberToken($value)
{
// If there is a token name...
if (!empty($this->getRememberTokenName()))
{
/*
* Set the property with the token name to the provided value
* Example: if $this->getRememberTokenName() returns 'myToken',
* this is equivalent to $this->myToken = $value
*/
$this->{$this->getRememberTokenName()} = $value;
}
}
public function getRememberTokenName()
{
return $this->rememberTokenName;
}
}
class Model
{
use Authenticatable;
private $myToken;
private $rememberTokenName = 'myToken';
public function __construct($myToken)
{
$this->myToken = $myToken;
}
public function getMyToken()
{
return $this->myToken;
}
}
$myModel = new Model('foo');
assert($myModel->getMyToken() == 'foo', 'Token should match constructor argument');
$myModel->setRememberToken('bar');
$updatedToken = $myModel->getMyToken();
assert($updatedToken == 'bar', 'Token should have been updated by trait');

Related

PHP - Assigning a string value to a variable that is type hinted as an enum

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

PHP Can an injected object access properties of the object it was injected into?

Does an injected object have any way to read it's parent's properties?
Here is an example:
This is a field object that holds the name and value of a field, and allows you to inject validation rules:
class field
{
public string $name;
public $value;
public array $validators;
public function __construct($name, $value = null)
{
$this->name = $name;
$this->value = $value;
}
public function addValidator(validatorInterface $validator): field
{
$this->validators[] = $validator;
return $this;
}
public function validate()
{
foreach ($this->validators as $validator) {
if (! $validator->validate()) {
return false;
}
}
return true;
}
}
And here are a couple validators:
interface validatorInterface
{
public function validate(): bool;
}
class stringValidator implements validatorInterface
{
public function validate(): bool
{
return is_string($this->value);
}
}
class lengthValidator implements validatorInterface
{
public function validate(): bool
{
return strlen($this->value) > 3;
}
}
Now let's take a test run:
$name = new field('name', 'Robert');
$name->addValidator(new stringValidator);
$name->addValidator(new lengthValidator);
echo $name->validate() ? 'valid' : 'invalid';
Obviously it fails because the validators have no idea what the field object's $value property is.
What comes to mind first is to create a setter in each validator that is used by the field object to pass the value to each validator:
class field
{
[...]
public function addValidator(validatorInterface $validator): field
{
$this->validators[] = $validator;
$validator->setValue($this->value); // <--- new line
return $this;
}
[...]
}
class stringValidator implements validatorInterface
{
private $value;
public function setValue($value): validatorInterface // <--- new method
{
$this->value = $value;
return $this;
}
public function validate(): bool
{
return is_string($this->value);
}
}
But the problem is that now I need to loop through every validator to inform them of the new field value any time it changes, which means that both the field instance and the validatorInterface instances are both holding a value that should be in sync, but may not be because they're not actually the same variable.
Is there any direct way to have the injected validators "just know" what the field instance's value property is?
Pass the field object to the validator instead of the property value because objects are always passed by reference:
class field
{
[...]
public function addValidator(validatorInterface $validator): field
{
$this->validators[] = $validator->setField($this);
return $this;
}
[...]
}
class stringValidator implements validatorInterface
{
private $field;
public function setField($field): validatorInterface
{
$this->field = $field;
return $this;
}
public function validate(): bool
{
return is_string($this->field->value);
}
}
than you can use it just like your example :
$name = new field('name', 'Robert');
$name->addValidator(new stringValidator);
$name->addValidator(new lengthValidator);
echo $name->validate() ? 'valid' : 'invalid';
You could pass the field object in the constructor of the validator but you would need to pass it each time you add a new validator, e.g.: $name->addValidator(new stringValidator($name)); but the above solution is better.

Yii2 getters & setters to format string to float

Ok so currently have this function in controller, which is called multiple times.
public function formatFloat($value)
{
return (float)sprintf('%0.6f', $value);
}
So I am trying to use getters and setters so I can just use
$model->$whatever;
and the formatting will be done.
In my model I have
public function getChargePeak()
{
return $this->charge_peak;
}
public function setChargePeak($value)
{
return $this->charge_peak = (float)sprintf('%0.6f', $value);
}
but when doing
$peak = $model->chargepeak;
var_dump($peak);die;
it is still returning as a string
If the charge_peak property is stored as string and you need a float in you app you should use
public function getChargePeak()
{
return floatval($this->charge_peak);
}
Anyway you should store the values in a coherent way as you use the values in your app ..
http://php.net/manual/en/function.floatval.php
So I suggest u another pattern: decorator and helpers. You should use a controller only to get data from request, prepare it for model and send it to view.
Formatting values is a helper logic. So create a new class
\common\helpers\Number.php
namespace common\helpers;
class Number
{
public static function formatFloat($value)
{
return (float)sprintf('%0.6f', $value);
}
}
Then create decorator for your model:
namespace common\models\decorators;
class YourModelDecorator
{
/**
* YourModel
*/
private $model;
public function __construct(YourModel $model)
{
$this->model = $model;
}
public function __get($name)
{
$methodName = 'get' . $name;
if (method_exists(self::class, $methodName)) {
return $this->$methodName();
} else {
return $this->model->{$name};
}
}
public function __call($name, $arguments)
{
return $this->model->$name($arguments);
}
public function getChargePeak()
{
return \common\helpers\Number::formatFloat($this->model->charge_peak);
}
}
and send it to view for example:
public function actionView($id)
{
$model = $this->loadModel($id);
$this->render('view', [
'model' => new \common\models\decorators\YourModelDecorator($model)
]);
}

Yii: How to refresh or unset or reset a model?

I need to refresh, reset or unset a model;
Normally, by using a for operation, the public static $k value should change, and id does change, but the tableName model method is called only once;
the value of tablename will always be 1, because that is the fisrt value of $i;
for($i=1;$i<=100;$i++){
VillageBuildingKXSlaveM::$server_id = 1;
VillageBuildingKXSlaveM::$k = $i;
VillageBuildingKXSlaveM::model()->findAllByAttributes(array());
}
<?php
class VillageBuildingKXSlaveM extends VillageBuildingKXM {
public static function model($className = __CLASS__) {
return parent::model($className);
}
public static $server_id;
public static $slave_db;
public static $k;
public function getDbConnection() {
self::$slave_db = Yii::app()->dbx;
if (self::$slave_db instanceof CDbConnection) {
self::$slave_db->active = false;
$config = require(Yii::app()->getBasePath() . '/config/main.php');
$connectionString = $config['components']['dbx']['connectionString'];
self::$slave_db->connectionString = sprintf($connectionString, self::$server_id);
self::$slave_db->setActive(true);
return self::$slave_db;
} else
throw new CDbException(Yii::t('yii', 'Active Record requires a "db" CDbConnection application component.'));
}
public function tableName() {
return 'village_building_k' . self::$k;
}
}
Try using
VillageBuildingKXSlaveM::model()->unsetAttributes();
to unset the attributes in a model
Or you can also pass the attributes name as arguments in the method like
VillageBuildingKXSlaveM::model()->unsetAttributes($attributes);
You can call
VillageBuildingKXSlaveM::model()->tableName();

How do I store an array of objects within an object in PHP?

I would like one of the attributes of my object to be an array of another type of object.
How do I represent this (i.e. public $someObjectArray;)
What would be the syntax to add an entry to this attribute?
What would be the syntax to reference the object?
To provide some (hopefully) useful context.
Lets assume that the object is property which has some attributes one of which is a number of tenants who will have their own properties like name, age etc...
class Tenant {
// properties, methods, etc
}
class Property {
private $tenants = array();
public function getTenants() {
return $this->tenants;
}
public function addTenant(Tenant $tenant) {
$this->tenants[] = $tenant;
}
}
If the Tenant model has some sort of identifiable property (id, unique name, etc), you could factor that in to provide better accessor methods, eg
class Tenant {
private $id;
public function getId() {
return $this->id;
}
}
class Property {
private $tenants = array();
public function getTenants() {
return $this->tenants;
}
public function addTenant(Tenant $tenant) {
$this->tenants[$tenant->getId()] = $tenant;
}
public function hasTenant($id) {
return array_key_exists($id, $this->tenants);
}
public function getTenant($id) {
if ($this->hasTenant($id)) {
return $this->tenants[$id];
}
return null; // or throw an Exception
}
}

Categories