Skip to content

Instantly share code, notes, and snippets.

@Antnee
Last active September 30, 2017 01:22
Show Gist options
  • Save Antnee/237334e1eb937892ad7a to your computer and use it in GitHub Desktop.
Save Antnee/237334e1eb937892ad7a to your computer and use it in GitHub Desktop.
PHP7 Structs
<?php
/**
* Ideas for how I'd like to see structs implemented in PHP7
*
* In this example, structs are more like lightweight objects that have no
* methods, just properties. They can be "instantiated" and all properties will be
* null until defined later, as per classes. It is however not possible to set a
* value to a property that was not defined in the struct originally.
*
* Structs can extend other structs, much like classes can. Properties can also
* have default values, just as classes can. All properties are public. It is
* possible to override previous default property values in the extending struct,
* as is done in classes.
*
* During instantiation, it is possible to set properties in a similar fashion to
* defining array keys and values, ie with named arguments.
*
* Structs are first class.
*/
namespace antnee;
struct User {
string $name;
string $email;
bool $active;
int $maxLogins;
}
$user = new User;
$user->name = 'Antnee';
$user->email = 'antnee@email.com';
$user->active = true;
$user->maxLogins = 'yes'; // Throw a catchable fatal error - incorrect type
$user->foo = 'bar'; // Throw a catchable fatal error - unknown property
/**
* Populating a Struct via a _constructor_ looks like a mix of object and array
*/
$user = new User(
'name' => 'Antnee',
'email' => 'antnee@email.com',
'active' => true,
'maxLogins' => 3,
);
/**
* Function taking a User struct as an argument
*/
function acceptUserStruct(User $struct): User {
var_dump($struct);
return $struct;
}
acceptUserStruct($user);
/**
* Extending a struct
*/
struct AdminUser extends User {
int $maxLogins = -1;
bool $admin = true;
}
$admin = new AdminUser(
'name' => 'Antnee',
'email' => 'antnee@email.com',
'active' => true
);
echo json_encode($admin);
/* Echoes:
{
"name": "Antnee",
"email": "antnee@email.com",
"active": true,
"maxLogins": -1,
"admin": true
}
*/
/**
* Pass in _anonymous_ struct as argument
*/
$name = 'Me';
$email = 'me@email.com';
$active = true;
$maxLogins = 5;
echo json_encode(
struct {
'name' => $name,
'email' => $email,
'active' => $active,
'maxLogins' => $maxLogins,
}
);
/* Echoes:
{
"name": "Me",
"email": "me@email.com",
"active": true,
"maxLogins": 5
}
*/
@gallna
Copy link

gallna commented Jul 2, 2015

I like it

@dshafik
Copy link

dshafik commented Jul 2, 2015

I like this a lot, especially constructor syntax. However Structs and Classes share the same namespace and this could be a frustration, I think maybe one of the follow two syntaxes would avoid that:

function acceptUserStruct(struct User $user): struct User {
    var_dump($user);
    return $user;
}

$struct = new struct User(
    'key' => 'value',
);

// or:
function acceptUserStruct(<User> $user): <User> {
    var_dump($user);
    return $user;
}

$struct = new <User> { // note curly braces here, could be parens but I felt it was ugly :P
     'key' => 'value'
};

Then, assuming this will be implemented using an underlying class similar to closures and generators, having Struct::fromArray() and Struct::toArray() methods would be great, either as statics on the underlying struct class (which could define a __toArray() and __fromArray() magic method, yes we're adding methods, this is terrible!) so you could do:

struct User { }

$userStruct = <User>::fromArray($array);
$userArray = <User>::toArray($userStruct);
// of
$userStruct = struct User::fromArray($array);
$userArray = struct User::toArray($userStruct);

Or as instance methods (my preference because ugh the above looks fugly):

$user = new struct User;
$user->fromArray($array);
$userArray = $user->toArray();
// or:

$user = new <User>;
$user->fromArray($array);
$userArray = $user->toArray();

I prefer the <StructName> syntax, but could clash with Generics if we ever go that round. Maybe {StructName} or something else?

@Rican7
Copy link

Rican7 commented Jul 2, 2015

@dshafik I see your point with the whole struct/class name clashing, but I'm not a fan of separating their identifier syntax. I think one of the strengths would be to treat them similarly and as a first class citizen. With namespaces, I feel like it wouldn't be a big deal either.

@Antnee
Copy link
Author

Antnee commented Jul 2, 2015

@dshafik - Agree about them occupying the same namespace. Originally I thought this is the same problem as with interfaces and traits, but there is no new operator on those, so there is a difference. However, you can still hint an interface or a class, so would it be such a bad thing? Keeping things consistent at least, would we not just do something like:

<?php
namespace antnee;

use classes/user;
use structs/user as userStruct;

function convertUserClassToStruct(user $user): userStruct {}

function convertUserStructToClass(userStruct $user): user {}

Or similar.

Ultimately, a struct is a datatype in the same was as an integer, an object, a string... anything. Mixing up the type hinting solution would probably confuse more. I would prefer that the interpreter just lets me know that I have a conflicting namespace. If it's down to the autoloader solution then maybe it needs to be suggested simply that a certain type takes precedence. Say, for example, that Drupal wanted to prioritise Class over Struct, let them do so in their autoloader. On the otherhand, if the FIG said "we should revise PSR-4 to support structs and structs take precedence over classes" then let them too.

I'm not happy about the ->toArray() and ->fromArray() purely because I feel that it's adding complexity, and would soon end up being an object. If the type passed into the constructor was an array then it should be implied to convert from array, and if you did $data = (array)$struct then I would hope that it would cast nicely from a struct into an array itself.

@Antnee
Copy link
Author

Antnee commented Jul 2, 2015

@dshafik - Realised that I'd forgotten to add the anonymous struct that I'd thought of a while back. Added in at #4:

<?php

/**
 * Pass in _anonymous_ struct as argument
 */
$name = 'Me';
$email = 'me@email.com';
$active = true;
$maxLogins = 5;

echo json_encode(
    struct {
        'name'      => $name,
        'email'     => $email,
        'active'    => $active,
        'maxLogins' => $maxLogins,
    }
);

/* Echoes:
{
    "name": "Me",
    "email": "me@email.com",
    "active": true,
    "maxLogins": 5
}
*/

Note that the anonymous struct uses braces. It works much the same as a closure. It could only be type hinted as a struct. Also, the type isn't specified here but is implied from the type of the variables that have been passed in. If you wanted to do strict type checking then you would need to define the struct earlier and instantiate it as in the original example.

Something else that I thought of: In this example that you gave:

function acceptUserStruct(struct User $user): struct User {
    var_dump($user);
    return $user;
}

I would support this only if struct was optional to assist the interpreter in understanding whether I meant the struct User or the class User if this was considered ambiguous, much like if you join two tables in MySQL and there are column names in both tables with the same name. But if this was the case, it would need to have a companion for a class IMO:

function acceptUserStruct(class User $user): class User {
    var_dump($user);
    return $user;
}

If you somehow have structs and classes with the same name and the same namespace, and it was allowed, I feel that making this an optional clarifier needs to work both ways

@Antnee
Copy link
Author

Antnee commented Jul 3, 2015

@dshafik and @Rican7 : We've just been looking at the namespacing issue, and we don't feel that it's any worse than the class/interface/trait issue, because:

<?php
class abc{}
interface abc{}

PHP Fatal error: Cannot redeclare class abc in test.php on line 3

<?php
class abc{}
trait abc{}

PHP Fatal error: Cannot redeclare class abc in test.php on line 3

<?php
trait abc{}
class abc{}

PHP Fatal error: Cannot redeclare class abc in test.php on line 3

So you can't have a trait, interface or class in the same namespace with the same name anyway. Not in PHP 5.x anyway. Unsure about PHP 7 (untested), but if it behaves the same then it would be consistent and everyone's happy, yes?

Thanks @gallna for looking into this

@steamboatid
Copy link

love it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment