Do understand some code using abstract class in php - php

I understand the concept of abstract class, but I saw a code in a book that I do not understand.
I will short the code and would like you help me to understand, I know what it does I just don't why is working.
Here I declared and abstract class DataObject and its contructor
abstract class DataObject {
protected $data = array();
public function __construct( $data ){
foreach ( $data as $key => $value )
{
if( array_key_exists( $key, $this->data ))
$this->data[$key] = $value;
}
}
}
Then I have this
class Member extends DataObject {
protected $data = array(
"username" => "",
"password" => ""
);
public function getInfo(){
echo "Usernarme: " . $this->data["username"] . " <br/>password: " . $this->data["password"];
}
}
So when I do this
$m= new Member( array(
"username" => "User",
"password" => "Some password" )
);
$m->getInfo();
I get
Usernarme: User
password: Some password
To be more specific.
Looks like since I did not create a constructor for the extended class is calling implicitly the father class, right?
How the constructor works in a way that it is validating the data array according to the Member array values?, I mean if when I create the Object
$m= new Member( array(
"username" => "User",
"password" => "Some password" )
);
Change the key "username" for "usernames" it won't assign the value "User" for example.
Thanks

In the parent constructor:
if( array_key_exists( $key, $this->data ))
$this->data[$key] = $value;
This prevents it from creating new keys. In the child class the keys "username" and "password" are defined, so those are the only keys that the constructor will allow to be written.

Looks like since I did not create a constructor for the extended class is calling implicitly the father class, right?
Not implicitly, but explictly. By "not creating a constructor" you didn't override the existing one.
How the constructor works in a way that it is validating the data array according to the Member array values?, I mean if when I create the Object
$m= new Member( array( "username" => "User", "password" => "Some password" ) );
Change the key "username" for "usernames" it won't assign the value "User" for example.
Yes. Because in the parent class you define, that the value should only be set, if the key exists, the non-existing key "usernames" is silently omitted.

Related

PHP - create strict typed Map dynamically (multidimensional)?

I need to be able to create strict typed maps dynamically. Like this:
$map = new Map( 'string,array<string,int>', [
'foo' => [
'bar' => 1
]
];
I have seen a lot of solutions for separate cases. All guides are teaching to create a class for each map, like Users_Map (to keep users there), Products_Map (to keep products there), Comments_Map (to keep comments there), etc.
But I don't want to have 3 classes (dozens in fact - for a big project) for each type of the map. I want to create a single class Map and then use it like this:
$users = new Map( 'User', {users data goes here} );
$products = new Map( 'int,Product', {products data goes here} );
$comments = new Map( 'User,array<Comment>', {comments data goes here} );
I would appreciate if somebody can advice me any existing repos. Otherwise I'll probably implement this on my own and will put here a link to my solution as an answer.
What you're looking for is called generics. PHP doesn't support this, although there has been an RFC calling for support for a few years.
If you really want to enforce strict typing on a custom map, you'd have to build it yourself. You could, for example, do something like this:
class Map {
private string $keyType;
private string $valueType;
private array $items;
public function __construct(string $keyType, string $valueType) {
$this->keyType = $keyType;
$this->valueType = $valueType;
}
public function set($key, $value) {
if (gettype($key) !== $this->keyType && !($key instanceof $this->keyType)) {
throw new TypeError("Key must be of type " . $this->keyType);
}
if (gettype($value) !== $this->valueType && !($value instanceof $this->valueType)) {
throw new TypeError("Value must be of type " . $this->valueType);
}
$this->items[$key] = $value;
}
public function get($key) {
if (gettype($key) !== $this->keyType) {
throw new TypeError("Key must be of type " . $this->keyType);
}
return $this->items[$key] ?? null;
}
public function all() {
return $this->items;
}
}
(of course, this particular implementation uses a regular array internally, so keyType is limited to types that are valid array keys. If you want to support other object types, some more interesting logic might be required)
The combination of gettype and instanceof will ensure this works for both simple and complex types. For example:
$map = new Map("string", "array");
$map->set("name", ["Boris", "Johnson"]);
print_r($map->all());
/*
Array
(
[name] => Array
(
[0] => Boris
[1] => Johnson
)
)
*/
$map->set("job", "Prime Minister");
// Fatal error: Uncaught TypeError: Value must be of type array
Or with a class as value type:
class User {
public string $firstName;
public string $lastName;
}
$user = new User();
$user->firstName = "Boris";
$user->lastName = "Johnson";
$map = new Map("string", User::class);
$map->set("pm", $user);
print_r($map->all());
/*
Array
(
[pm] => User Object
(
[firstName] => Boris
[lastName] => Johnson
)
)
*/
If you also want to support nested generics, like in your example array<string,int>, that becomes more complicated. In that case, as soon as someone passes an array as a value, you'd have to manually check all items in the array to ensure all array keys are strings and all array values are integers. It's possible, but for larger arrays it will be a significant performance hit.
Although you could use a nested Map like this one if you extend it to enforce the types:
class StringIntMap extends Map {
public function __construct() {
parent::__construct("string", "integer");
}
}
$map = new Map("string", StringIntMap::class);

In PHP can a StdClass's value be the value of another key?

Not sure how to word this question.
Is it possible to create a new StdClass and have one value be the value of another key(property)?
Without acreating a class
$myObject = (object) [
'base_url' => 'http://www.google.com/',
'route' => $this->base_url . '/some/deeper/path'
];
I know this code is incorrect, but focusing on replacing $this just for getting the concept across.
Is this even possible?
The problem is your array creation is done as a single step, and the source of base_url doesn't exist yet, only as seperate actions, you need to set to get :)
$myObject = new stdClass();
$myObject->base_url = 'http://www.google.com';
$myObject->route = $myObject->base_url . '/some/deeper/path';
Edit: you could use a varaible assignment in the middle of your one liner...
$myObject = (object) [
'base_url' => $base_url = 'http://www.google.com/',
'route' => $base_url . '/some/deeper/path'
];
//unset( $base_url );
Because the action of $base_url = 'http://www.google.com/' happens first before the object creation, thus making a two step process.
You can make a dynamic PHP property using the magic __get method. For example:
<?php
class MyClass {
public $base_url = 'http://www.google.com/';
public function __get($name) {
if ($name == 'route') {
return rtrim($this->base_url,'/') . '/some/deeper/path';
}
throw new \Exception("Property '$name' is not set");
}
}
Do this on stdClass you could subclass it. However stdClass is just like an initial base class anyway.

optimising php validator to OOP

I need a php validator class that validates user inputs.
I want it to be able to accept an assoc array of fields => values like:
array(
"username" => "Alex",
"email_address" => "###3423£alex#my.mail.com"
);
and then return an array of errors like this:
array(
"username" => "",
"email_address" => "Invalid Email Address"
);
But I'm really struggling on HOW the hell I'm going to do this!
I've read countless pages on PHP validators and read that the best way to do this is with the strategy pattern. But i dont know how??
Like... This is what I've got so far:
class Validator {
private
$_errors,
$_fields,
static private $_map = array (
"firstname" => "name",
"surname" => "name",
"agency_name" => "name",
"agency_office" => "name",
"username" => "username",
"email_address" => "email_address",
);
public function __construct( array $fields ) {
$this->_fields = $fields;
}
public function validate() {
foreach ( $this->_fields as $field => $value ) {
if ( method_exists( __CLASS__, self::$_map[$field] ) ) {
if ( in_array( $field, self::$_map ) ) {
$this->{self::$_map[$field]}( $field, $value );
}
}
else {
die( " Unable to validate field $field" );
}
}
}
public function get_errors() {
return $this->_errors;
}
private function name( $field, $value ) {
if ( !preg_match( "/^[a-zA-Z]{2,50}$/", $value ) ) {
$this->errors[$field] = "Invalid. Must be 2 to 50 alphanumerical characters";
}
}
private function username( $field, $value ) {
if ( !preg_match( "/^[a-zA-Z0-9_\-]{10,50}$/", $value ) ) {
$this->errors[$field] = "Invalid. Must be 10 to 50 characters. Can contain digits, characters, _ (underscore) and - (hyphen)";
}
}
private function password( $field, $value ) {
if ( !preg_match( "/^[a-zA-Z0-9\.\-]{8,30}$/", $value ) ) {
$this->_errors[$field] = "Invalid. Must be 8 to 30 characters. Can contain digits, characters, . (full stop) and - (hyphen)";
}
}
private function email_address( $field, $value ) {
if ( !filter_var( $value, FILTER_VALIDATE_EMAIL ) ) {
$this->_errors[$field] = "Invalid Email Address";
}
}
}
The problems with this is, it doesn't even consider database connections for like, already registered usernames,
Also is doesn't match passwords
I've just got coders block at the moment and its destroying me on the inside :(
Can anybody give a an explaination of the classes required and functions each class will need to do?
I really need the inputs and outputs to be in the format already explained though!
Thankyou Very Much Internet People!
As a part of the my MVC I have solved the same problem. I could give you a listing, but in a few lines try to describe how.
I got 3 base classes Form, Validator, Field, each of object of this classes configuring through one YAML file, structured somehow like this:
name: // field name
i18n: [ ru, en ] // is the field i18n
field:
class: Base // class to use for field
options: { specific_save: true } // options from available (defined in class)
attributes: { } // attributes, for HTML rendering
validator:
class: String // Class to validate with
options: { required: true, max: 100 } // options for validator
So, lets start with Form, when object is constructing the form takes the YAML file described above, and due to that configuration creates fields. Something like this:
// Imlement this function to configure form;
foreach ($this->_config as $f => $c)
{
$class = '\\Lighty\\Form\\Field\\' . (isset($c['field']['class']) && $c['field']['class'] ? $c['field']['class'] : 'Base');
$o = isset($c['field']['options']) && is_array($c['field']['options']) ? $c['field']['options'] : array();
$a = isset($c['field']['attributes']) && is_array($c['field']['attributes']) ? $c['field']['attributes'] : array();
$field = new $class($this, $o, $a);
$field->setName($f);
$class = '\\Lighty\\Form\\Validator\\' . (isset($c['validator']['class']) && $c['validator']['class'] ? $c['validator']['class'] : 'Base');
$o = isset($c['validator']['options']) && is_array($c['validator']['options']) ? $c['validator']['options'] : array();
$m = isset($c['validator']['messages']) && is_array($c['validator']['messages']) ? $c['validator']['messages'] : array();
$field->setValidator($validator = new $class($field, $o, $m));
if (isset($this->_options['default'][$f]))
{
$field->setValue($this->_options['default'][$f]);
}
if (isset($c['i18n']))
{
if (is_array($c['i18n']))
{
$field->setCultures($c['i18n']);
}
$field->setI18n((bool) $c['i18n']);
}
$this->addField($field);
So, now we have form with fields and validator for each field, then to validate I use this mechanism:
Form goes through each field, calling validate() method,
Field (got the binded value) call validate($value) method of binded Validator, passing the stored value. Inside this method Validator calls the validateOption() method, in which there is a simple switch for each options, for example:
switch ($o)
{
case 'required':
$valid = $state && trim($value) != '' || !$state;
break;
default:
return \warning(sprintf('Undefined validator option "%s" in %s validator class', $o, get_class($this->getField()->getValidator())), null);
}
Here you can see validating on required option. If I need more validators, I extend class of the Base validator, defined few more options, and redefine validateOption(), where in default statement of the option's switch put parent::validateOption(). So specified options validates in new class, and old one in base validator Class.
If there any questions... You're welcome.

Building an array from unknown object properties

i'm trying to build an array from an Object in PHP. I only want certain properties from the object but I don;t know what they will be each time. The names of the properties I need are stored in an array. Here is how my code works currently:
// Hard-coded attributes 'colour' and 'size'
while ($objVariants->next())
{
$arrVariants[] = array
(
'pid' => $objVariants->pid,
'size' => $objVariants->size,
'colour' => $objVariants->colour,
'price' => $objVariants->price
);
}
Instead of hard coding the attributes (colour and size) I want to use variables, this is because it may not always be colour and size depending on what the user has set in the CMS. For example:
$arrVariantAttr = $this->getVariantAttr(); // Get the names of the custom variants and put them in an array e.g colour, size
while ($objVariants->next())
{
$arrVariants[] = array
(
'pid' => $objVariants->pid,
foreach($arrVariantAttr as $attr)
{
$attr['name'] => $objVariants-> . $attr['name']; // Get each variant out of the object and put into an array
}
'price' => $objVariants->price
);
}
The above code doesn't work, but hopefully it illustrates what i'm trying to do. Any help would be appreciated, thank you!
You could use get_object_vars() to get all variables of an object:
$arrVariants[] = get_object_vars($objVariants);
In order to exclude specific properties from the object you could do like this:
$arrVariants = get_object_vars($objVariants);
// array containing object properties to exclude
$exclude = array('name');
// walk over array and unset keys located in the exclude array
array_walk($arrVariants, function($val,$key) use(&$arrVariants, $exclude) {
if(in_array($key, $exclude)) {
unset($arrVariants[$key]);
}
});
You could create an array in the object containing the attributes:
$objVariants->attr['pid']
You can also use magic methods to make you object array like.
It sounds like what you really want is sub-classes or a Factory pattern.
For instance you could have a basic product object
class Product {
protected $_id;
protected $_sku;
protected $_name;
...
etc.
//getters and setters
etc.
}
... and then use sub-classes to extend that product
final class Book extends Product {
private $_isbn;
private $_language;
private $_numPages;
...
etc.
public function __construct() {
parent::__construct();
}
//getters and setters
etc.
}
That way your product types have all the attributes they need and you don't need to try and run around with an "attributes" array - though your CMS needs to be able to support product types (so that if someone wants to add a new book, the fields relevant to books appear in the CMS)... it's just a slightly more OO approach to the problem.
You could then factory pattern it; something like (a really basic example):
class ProductFactory {
const TYPE_BOOK = 'Book';
const TYPE_CD = 'CD';
const TYPE_DVD = 'DVD';
...
etc.
public static function createProduct($sProductType) {
if(class_exists($sProductType)) {
return new $sProductType();
}
else {
//throw an exception
}
}
}
You can then generate new products with something like:
$oWarAndPeace = ProductFactory::createProduct('Book')
or better yet:
$oWarAndPeace = ProductFactory::createProduct(ProductFactory::TYPE_BOOK)
Try something like this:
$arrVariants[] = Array(
'pid' => $objVariants->pid,
'price' => $objVariants->price
);
while( $objVariants->next() )
{
foreach( $arrVariantAttr as $attr )
{
end($arrVariants)[$attr['name']] = $objVariants->$attr['name'];
}
}

prevent parent class 'forgetting' local vars set by children: php

I have this class
class Validator implements iValidation{
protected
$_fields,
$_errors;
public function __construct($fields){
$this->_errors = array();
$this->_fields = $fields;
}
public function validate(){
$map = unserialize( iValidation::map );
foreach ($this->_fields as $field_type => $data){
if ( array_key_exists( $field_type, $map ) ){
$class = "Validate_{$map["$field_type"]}" ;
$object = new $class($data);
$object->validate();
unset($object);
}
}
}
public function getErrors(){
return $this->_errors;
}
}
Now this class loops through an array given in the format
$admin_test_data = array(
"firstname" => "Alex1",
"surname" => "Morley-Finch",
"username" => "alex123",
"password" => "cheese",
"re_password" => "cheese",
"email_address" => "alex54353hotmail.co.uk",
"user_type" => "ADMIN",
"add_admin" => "Add Admin",
);
I have a map that describes the type of validation on each field that is declared like so:
define(
"VALIDATION_MAP",
serialize(
array(
// FIELD => VALIDATION TYPE
"firstname" => "Name",
"surname" => "Name",
"agency_name" => "Agency_Name",
"agency_office" => "Name",
"username" => "Username",
"email_address" => "Email_Address",
"password" => "Password",
)
)
);
interface iValidation{
public function __construct($data);
public function validate();
const map = VALIDATION_MAP;
}
And I have sub classes One of which is like this:
class Validate_Name extends Validator implements iValidation{
private $_data;
public function __construct($data){
$this->_data = $data;
}
public function validate(){
$data = $this->_data;
$length = strlen($data);
if ( $length > 40 ){
$this->_errors[] = "Cannot be more than 40 characters";
}
if ( $length < 3 ){
$this->_errors[] = "Cannot be less than 3 characters";
}
if ( !preg_match("/^[a-zA-Z]+$/", $data) ){
$this->_errors[] = "A-Z characters only";
}
}
}
And the whole framework is used like so:
$validator = new Validator($admin_test_data);
$validator->validate(); // ^ defined earlier ^^^
$errors = $validator->getErrors();
Now after all that background info... lets get to the problem.
I'm probably doing some EXTREMELY stupid but I can't figure it out and Google isn't doing a good job of enlightening me ( or I'm not doing a good job of asking google )
The sub classes reference $this->_errors[] = "This is the error"; and when using die( var_dump( $this->_errors ) ); inside the sub class, the errors for that class appear as they should.
However if I call die( var_dump( $validator->getErrors() ) ); outside the classes, the array is empty.
Asif the parent has forgotten all the values the children set?
Whats going on?
For each field you validate in Validator, you create an instance of the required implementation of iValidation.
Now the child validator validates, and it finds errors. It adds those errors to its _errors instance attribute.
When it's done validating, the child instance is discarded. And that's where the problem lies, since the errors found were assigned to the child instance's _errors attribute, not that of the parent instance.
Nothing ever gets assigned to Validator::_errors, because those properties are in different objects.

Categories