-
Notifications
You must be signed in to change notification settings - Fork 0
Class: Nether\Object\Prototype
The Prototype Object a self-sealing stem object capable of translating schemas and ensuring that properties exist with default values if needed. Using this class as the parent enables the attribute based functionality all the way down.
$Object = new Nether\Object\Prototype(
array|object|null $Input,
array|object|null $Defaults,
int|null $Flags
);
Without any properties, attributes, or additional arguments, you get an object
back with the properties you asked it to have. Here you can see MySQL gave us
back a number as string and it will continue to be so after this. The result
is basically if you had just done (object)$array
except you get an object of
the class of choice, in this case User.
class User
extends Nether\Object\Prototype {
}
$RowFromDB = [
'user_id'=> '1',
'user_name'=> 'bob',
'user_email'=> 'bmajdak-at-php-dot-net',
'user_title'=> 'Chief Iconoclast'
];
var_dump(new User($RowFromDB));
object(User)#1 (4) {
["user_id"]=> string(1) "1"
["user_name"]=> string(3) "bob"
["user_email"]=> string(22) "bmajdak-at-php-dot-net"
["user_title"]=> string(16) "Chief Iconoclast"
}
Changing nothing except adding typed properties to the class will cause it
to typecast the value for you. In the example where MySQL gave us back a string
numeral '1' which will then be casted to (int)'1'
for you. Only the core PHP
types can be casted - mixed
and classes/interfaces currently cannot be.
class User
extends Nether\Object\Prototype {
public int $user_id;
public string $user_name;
public string $user_email;
public string $user_title;
}
var_dump(new User($RowFromDB));
object(User)#2 (4) {
["user_id"]=> int(1)
["user_name"]=> string(3) "bob"
["user_email"]=> string(22) "bmajdak-at-php-dot-net"
["user_title"]=> string(16) "Chief Iconoclast"
}
We all know that Dave asked absolutely nobody when he designed that database table, and now you are stuck having to type that snake-case crap the rest of your life. Using attributes the input data can be remapped to an API that will not damage your calm. Technically this makes it half of an ORM.
class User
extends Nether\Object\Prototype {
#[Nether\Object\Meta\PropertyOrigin('user_id')]
public int $ID;
#[Nether\Object\Meta\PropertyOrigin('user_name')]
public string $Name;
#[Nether\Object\Meta\PropertyOrigin('user_email')]
public string $Email;
#[Nether\Object\Meta\PropertyOrigin('user_title')]
public string $Title;
}
var_dump(new User($RowFromDB));
object(User)#3 (4) {
["ID"]=> int(1)
["Name"]=> string(3) "bob"
["Email"]=> string(22) "bmajdak-at-php-dot-net"
["Title"]=> string(16) "Chief Iconoclast"
}
The second argument to the constructor is an array of default values that should be filled into the object if the data source was missing something. In this example our MySQL data had no user_status field.
class User
extends Nether\Object\Prototype {
#[Nether\Object\Meta\PropertyOrigin('user_id')]
public int $ID;
#[Nether\Object\Meta\PropertyOrigin('user_name')]
public string $Name;
#[Nether\Object\Meta\PropertyOrigin('user_email')]
public string $Email;
#[Nether\Object\Meta\PropertyOrigin('user_title')]
public string $Title;
#[Nether\Object\Meta\PropertyOrigin('user_status')]
public string $Status;
}
$Defaults = [
'Status' => 'Probably Cool'
];
var_dump(new User($RowFromDB));
var_dump(new User($RowFromDB, $Defaults));
object(User)#4 (4) {
["ID"]=> int(1)
["Name"]=> string(3) "bob"
["Email"]=> string(22) "bmajdak-at-php-dot-net"
["Title"]=> string(16) "Chief Iconoclast"
["Status"]=> uninitialized(string)
}
object(User)#5 (5) {
["ID"]=> int(1)
["Name"]=> string(3) "bob"
["Email"]=> string(22) "bmajdak-at-php-dot-net"
["Title"]=> string(16) "Chief Iconoclast"
["Status"]=> string(13) "Probably Cool"
}
The third argument to the constructor is a flagset that can change some of the behaviours during construction. By default if properties have not been directly mapped in the class the additional ones will be added on the fly, which makes them both public and mixed type. Using the Strict flags you can have it only include the properties that are hardcoded into the class definition.
use Nether\Object\Prototype;
use Nether\Object\Prototype\Flags;
use Nether\Object\Meta\PropertyOrigin;
class User
extends Prototype {
#[PropertyOrigin('user_id')]
public int $ID;
#[PropertyOrigin('user_name')]
public string $Name;
}
var_dump(new User(
$RowFromDB,
$Defaults
));
var_dump(new User(
$RowFromDB,
$Defaults,
(Flags::StrictInput | Flags::StrictDefault)
));
object(User)#6 (4) {
["ID"]=> int(1)
["Name"]=> string(3) "bob"
["Status"]=> string(13) "Probably Cool"
["user_email"]=> string(22) "bmajdak-at-php-dot-net"
["user_title"]=> string(16) "Chief Iconoclast"
}
object(User)#7 (2) {
["ID"]=> int(1)
["Name"]=> string(3) "bob"
}
Properties of object types that do not require anything fancy passed to the constructors can be instantiated automatically using an attribute. Anything given to the attribute will be passed to the constructor.
The first example constructs a new Datastore with no arguments. The second example constructs a new Datastore giving it an array of data as its first argument.
use Nether\Object\Prototype;
use Nether\Object\Meta\PropertyObjectify;
class One
extends Prototype {
#[PropertyObjectify]
public Nether\Object\Datastore $List;
}
class Two
extends Prototype {
#[PropertyObjectify(['one','two','three'])]
public Nether\Object\Datastore $List;
}
var_dump(new One);
var_dump(new Two);
object(One)#1 () {
["List"]=> object(Nether\Object\Datastore)#5 (5) {
["Data":protected]=> array(0) {
}
}
}
object(One)#1 () {
["List"]=> object(Nether\Object\Datastore)#5 (5) {
["Data":protected]=> array(3) {
[0]=> string(3) "one"
[1]=> string(3) "two"
[2]=> string(5) "three"
}
}
}
When the Prototype constructor finishes it calls the OnReady
method on that object.
If more complex operations are needed to instantiate a new object then this would be
a good place to do that.
OnReady methods take a single ConstructArgs argument that contains the Input, Defaults, and Flags from the original point of construction.
use Nether\Object\Datastore;
use Nether\Object\Prototype;
use Nether\Object\Prototype\ConstructArgs;
class Three
extends Prototype {
public Datastore
$List;
protected function
OnReady(ConstructArgs $Args):
void {
// build a new list, filter some values out, and sort it.
$this->List = (
(new Datastore([ 'one', 'two', 'three', 'four' ]))
->Filter(fn($Val)=> ($Val !== 'three'))
->Sort(fn($A, $B)=> ($A <=> $B))
);
return;
}
}
object(One)#1 () {
["List"]=> object(Nether\Object\Datastore)#5 (5) {
["Data":protected]=> array(3) {
[3]=> string(4) "four"
[0]=> string(3) "one"
[1]=> string(3) "two"
}
}
}