I'm not seeing a problem here:
class A {
public int $x;
public ?int $y = null;
public int $z = 42;
public ?int $u;
public ?datetime $v;
public datetime $w;
}
$a = new A;
var_dump($a->x); // 0 + notice
var_dump($a->y); // null
var_dump($a->z); // 42
var_dump(isset($a->z)); // true
unset($a->z);
var_dump(isset($a->z)); // false
var_dump($a->z); // 0 + notice
var_dump($a->u); // null + notice
var_dump($a->v); // null + notice
var_dump($a->w); // Fatal error, uninitialized...
var_dump(isset($a->x)); // false
var_dump(isset($a->y)); // false
var_dump(isset($a->u)); // false
var_dump(isset($a->v)); // false
var_dump(isset($a->w)); // false
Regards
Thomas
public ?int $y = null;
public int $z = 42;
public ?int $u;
public ?datetime $v;
public datetime $w;
}$a = new A;
var_dump($a->x); // 0 + notice
var_dump($a->y); // null
var_dump($a->z); // 42
unset($a->z);
var_dump($a->z); // 0 + notice
var_dump($a->u); // null + notice
var_dump($a->v); // null + notice
var_dump($a->w); // Fatal error, uninitialized...
Fleshgrinder wrote on 25.05.2016 23:38:
Following "Type safety is the goal of this RFC, not validating objects.", it
would be better to do implicit casting for uninitialized properties (whenever
implicit casting is possible) or use null for nullable types:class A {
public int $x;
public ?int $y = null;
public int $z = 42;
public ?int $u;
public ?datetime $v;
public datetime $w;
}$a = new A;
var_dump($a->x); // 0 + notice
var_dump($a->y); // null
var_dump($a->z); // 42
unset($a->z);
var_dump($a->z); // 0 + notice
var_dump($a->u); // null + notice
var_dump($a->v); // null + notice
var_dump($a->w); // Fatal error, uninitialized...This was proposed many times but it renders checks whether something was
initialized or not completely useless and is not really a solution to
the problem at hand.--
Richard "Fleshgrinder" Fussenegger
var_dump($a->z); // 42
var_dump(isset($a->z)); // true
unset($a->z);
$a->z does not exist at this point
var_dump(isset($a->z)); // false
We know that isset is broken so if $a->z is null we also get false ...
perhaps the 'fault' that isset does not actually work properly is
biting? But since $a->z does not exist false avoids an error.
var_dump($a->z); // 0 + notice
The notice should be 'variable does not exist' as at this point $a->z
has no context at all!
--
Lester Caine - G8HFL
Contact - http://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - http://lsces.co.uk
EnquirySolve - http://enquirysolve.com/
Model Engineers Digital Workshop - http://medw.co.uk
Rainbow Digital Media - http://rainbowdigitalmedia.co.uk
var_dump($a->z); // 0 + notice
The notice should be 'variable does not exist' as at this point $a->z
has no context at all!
If we keep the private flag of a property after unset(), we should also keep the type of a property after unset(), e.g.
class test {
private $z = 42;
public function __construct() {
unset($this->z);
}
}
$a = new test();
$a->z = 21; // fatal: Cannot access private property...
So after unset, $a->z should still have context.
Regards
Thomas
Lester Caine wrote on 26.05.2016 00:13:
var_dump($a->z); // 42
var_dump(isset($a->z)); // true
unset($a->z);
$a->z does not exist at this pointvar_dump(isset($a->z)); // false
We know that isset is broken so if $a->z is null we also get false ...
perhaps the 'fault' that isset does not actually work properly is
biting? But since $a->z does not exist false avoids an error.var_dump($a->z); // 0 + notice
The notice should be 'variable does not exist' as at this point $a->z
has no context at all!--
Lester Caine - G8HFLContact - http://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - http://lsces.co.uk
EnquirySolve - http://enquirysolve.com/
Model Engineers Digital Workshop - http://medw.co.uk
Rainbow Digital Media - http://rainbowdigitalmedia.co.uk
Of course isset() has a special behaviour. So having some kind of real_isset() or initialized() would be nice.
Regards
Thomas
Thomas Bley wrote on 26.05.2016 00:38:
var_dump($a->z); // 0 + notice
The notice should be 'variable does not exist' as at this point $a->z
has no context at all!If we keep the private flag of a property after unset(), we should also keep
the type of a property after unset(), e.g.class test {
private $z = 42;public function __construct() { unset($this->z); }
}
$a = new test();
$a->z = 21; // fatal: Cannot access private property...So after unset, $a->z should still have context.
Regards
ThomasLester Caine wrote on 26.05.2016 00:13:
var_dump($a->z); // 42
var_dump(isset($a->z)); // true
unset($a->z);
$a->z does not exist at this pointvar_dump(isset($a->z)); // false
We know that isset is broken so if $a->z is null we also get false ...
perhaps the 'fault' that isset does not actually work properly is
biting? But since $a->z does not exist false avoids an error.var_dump($a->z); // 0 + notice
The notice should be 'variable does not exist' as at this point $a->z
has no context at all!--
Lester Caine - G8HFLContact - http://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - http://lsces.co.uk
EnquirySolve - http://enquirysolve.com/
Model Engineers Digital Workshop - http://medw.co.uk
Rainbow Digital Media - http://rainbowdigitalmedia.co.uk
I'm not seeing a problem here:
class A {
public int $x;
public ?int $y = null;
public int $z = 42;
public ?int $u;
public ?datetime $v;
public datetime $w;
}$a = new A;
var_dump($a->x); // 0 + notice
var_dump($a->y); // null
var_dump($a->z); // 42
var_dump(isset($a->z)); // true
unset($a->z);
var_dump(isset($a->z)); // false
var_dump($a->z); // 0 + notice
var_dump($a->u); // null + notice
var_dump($a->v); // null + notice
var_dump($a->w); // Fatal error, uninitialized...var_dump(isset($a->x)); // false
var_dump(isset($a->y)); // false
var_dump(isset($a->u)); // false
var_dump(isset($a->v)); // false
var_dump(isset($a->w)); // false
Is the file containing these examples in liberal mode?
What changes if declare(strict_types=1) precedes $a = new A;?
Tom
I think strict_types=1 should give a fatal error for accessing non-initialized typed properties, instead of notice.
Example:
declare(strict_types=1);
class A {
public int $x;
public ?int $y = null;
public int $z = 42;
public ?int $u;
public ?datetime $v;
public datetime $w;
}
$a = new A;
var_dump($a->x); // Fatal error, uninitialized...
var_dump($a->y); // null
var_dump($a->z); // 42
var_dump(isset($a->z)); // true
unset($a->z);
var_dump(isset($a->z)); // false
var_dump($a->z); // Fatal error, uninitialized...
var_dump($a->u); // Fatal error, uninitialized...
var_dump($a->v); // Fatal error, uninitialized...
var_dump($a->w); // Fatal error, uninitialized...
var_dump(isset($a->x)); // false
var_dump(isset($a->y)); // false
var_dump(isset($a->u)); // false
var_dump(isset($a->v)); // false
var_dump(isset($a->w)); // false
Regards
Thomas
Tom Worster wrote on 26.05.2016 15:53:
I'm not seeing a problem here:
class A {
public int $x;
public ?int $y = null;
public int $z = 42;
public ?int $u;
public ?datetime $v;
public datetime $w;
}$a = new A;
var_dump($a->x); // 0 + notice
var_dump($a->y); // null
var_dump($a->z); // 42
var_dump(isset($a->z)); // true
unset($a->z);
var_dump(isset($a->z)); // false
var_dump($a->z); // 0 + notice
var_dump($a->u); // null + notice
var_dump($a->v); // null + notice
var_dump($a->w); // Fatal error, uninitialized...var_dump(isset($a->x)); // false
var_dump(isset($a->y)); // false
var_dump(isset($a->u)); // false
var_dump(isset($a->v)); // false
var_dump(isset($a->w)); // falseIs the file containing these examples in liberal mode?
What changes if declare(strict_types=1) precedes $a = new A;?
Tom
Hi Thomas,
On the face of it, I'm not enthusiastic to introduce new magic numbers
(which would be false, 0, 0.0, "", and [], right?) that PHP assigns when
coercing a typed, uninitialized property read by a file in liberal mode.
This is like taking the most confusing thing about 7.0's dual-mode, scalar
type declaration of function arguments and boosting the confusion power. I
would want a new name for this complement-of-strict mode. "Weak" and
"liberal" don't quite do it. Promiscuous mode? ;)
Tom
I think strict_types=1 should give a fatal error for accessing
non-initialized typed properties, instead of notice.
Example:declare(strict_types=1);
class A {
public int $x;
public ?int $y = null;
public int $z = 42;
public ?int $u;
public ?datetime $v;
public datetime $w;
}$a = new A;
var_dump($a->x); // Fatal error, uninitialized...
var_dump($a->y); // null
var_dump($a->z); // 42
var_dump(isset($a->z)); // true
unset($a->z);
var_dump(isset($a->z)); // false
var_dump($a->z); // Fatal error, uninitialized...
var_dump($a->u); // Fatal error, uninitialized...
var_dump($a->v); // Fatal error, uninitialized...
var_dump($a->w); // Fatal error, uninitialized...var_dump(isset($a->x)); // false
var_dump(isset($a->y)); // false
var_dump(isset($a->u)); // false
var_dump(isset($a->v)); // false
var_dump(isset($a->w)); // falseRegards
ThomasTom Worster wrote on 26.05.2016 15:53:
I'm not seeing a problem here:
class A {
public int $x;
public ?int $y = null;
public int $z = 42;
public ?int $u;
public ?datetime $v;
public datetime $w;
}$a = new A;
var_dump($a->x); // 0 + notice
var_dump($a->y); // null
var_dump($a->z); // 42
var_dump(isset($a->z)); // true
unset($a->z);
var_dump(isset($a->z)); // false
var_dump($a->z); // 0 + notice
var_dump($a->u); // null + notice
var_dump($a->v); // null + notice
var_dump($a->w); // Fatal error, uninitialized...var_dump(isset($a->x)); // false
var_dump(isset($a->y)); // false
var_dump(isset($a->u)); // false
var_dump(isset($a->v)); // false
var_dump(isset($a->w)); // falseIs the file containing these examples in liberal mode?
What changes if declare(strict_types=1) precedes $a = new A;?
Tom
it's not so magic, rather think of (int)null, (float)null, (string)null, (array)null, etc.
Typed properties could be defined as nullable by default, but I think that makes userland code much more ugly.
Regards
Thomas
Tom Worster wrote on 26.05.2016 18:44:
Hi Thomas,
On the face of it, I'm not enthusiastic to introduce new magic numbers
(which would be false, 0, 0.0, "", and [], right?) that PHP assigns when
coercing a typed, uninitialized property read by a file in liberal mode.This is like taking the most confusing thing about 7.0's dual-mode, scalar
type declaration of function arguments and boosting the confusion power. I
would want a new name for this complement-of-strict mode. "Weak" and
"liberal" don't quite do it. Promiscuous mode? ;)Tom
I think strict_types=1 should give a fatal error for accessing
non-initialized typed properties, instead of notice.
Example:declare(strict_types=1);
class A {
public int $x;
public ?int $y = null;
public int $z = 42;
public ?int $u;
public ?datetime $v;
public datetime $w;
}$a = new A;
var_dump($a->x); // Fatal error, uninitialized...
var_dump($a->y); // null
var_dump($a->z); // 42
var_dump(isset($a->z)); // true
unset($a->z);
var_dump(isset($a->z)); // false
var_dump($a->z); // Fatal error, uninitialized...
var_dump($a->u); // Fatal error, uninitialized...
var_dump($a->v); // Fatal error, uninitialized...
var_dump($a->w); // Fatal error, uninitialized...var_dump(isset($a->x)); // false
var_dump(isset($a->y)); // false
var_dump(isset($a->u)); // false
var_dump(isset($a->v)); // false
var_dump(isset($a->w)); // falseRegards
ThomasTom Worster wrote on 26.05.2016 15:53:
I'm not seeing a problem here:
class A {
public int $x;
public ?int $y = null;
public int $z = 42;
public ?int $u;
public ?datetime $v;
public datetime $w;
}$a = new A;
var_dump($a->x); // 0 + notice
var_dump($a->y); // null
var_dump($a->z); // 42
var_dump(isset($a->z)); // true
unset($a->z);
var_dump(isset($a->z)); // false
var_dump($a->z); // 0 + notice
var_dump($a->u); // null + notice
var_dump($a->v); // null + notice
var_dump($a->w); // Fatal error, uninitialized...var_dump(isset($a->x)); // false
var_dump(isset($a->y)); // false
var_dump(isset($a->u)); // false
var_dump(isset($a->v)); // false
var_dump(isset($a->w)); // falseIs the file containing these examples in liberal mode?
What changes if declare(strict_types=1) precedes $a = new A;?
Tom
If you want, you can easily write a backwards-compatible new class that
uses declared type properties with
public int $property = null;
And for those pesky third party libraries that forgot to set = NULL
on
their property definition there's a backwards compatible accessor too.
$foo = $obj->bar ?? NULL;
Sarcasm aside, I still can't figure out how fundamentally changing how
people interact with uninitialized properties like this improves developer
experience. Can someone explain a case where this is better and catches a
bug or something? Since this is a new feature I would assume its not
covered by "BC" but this seems like a painful gotcha for people developing
across typed and untyped code.
Sarcasm aside, I still can't figure out how fundamentally changing how
people interact with uninitialized properties like this improves
developer experience. Can someone explain a case where this is better and
catches a bug or something? Since this is a new feature I would assume
its not covered by "BC" but this seems like a painful gotcha for people
developing across typed and untyped code.
Talk of improving developer experiences is too subjective for me. I only
want to say that one option we have is to require that a PHP property with
a type declaration must also have an initial value declaration. This
obviates some confusion, which is a good thing in my opinion.
I understand that it will be surprising to some that the lazy old public $var;
(that initializes to null if it is read before written to) is not
available if you insert a type declaration. Such surprise will dissipate
quickly if the compiler rejects such cases, one way or the other: either
don't declare type or declare type plus initial value.
If I'm wrong in this estimation and we in fact need to protect developers
from the pain of this experience then I'd prefer to reject typed
properties for the time being.
Tom
The problem is a completely different one, how should the following code
behave?
class A {
public int $x;
}
(new A)->x;
The property has no value assigned but it is being accessed. The current
PHP behavior is to simply initialize it with null. But this is
impossible according to the type definition.
There are not many ways to handle this. I think we already had all of
them proposed:
- Fatal error after __construct was called.
- Fatal error and abort.
- Initialize with appropriate type.
- Initialize with null.
Option 0. is out the window because it creates endless edge cases.
Option 1. is extremely brutal and not necessarily what we want (lazy,
anyone?).
Option 2. has a huge problem with objects because it cannot initialize
e.g. a \Fleshgrinder\Custom\SuperClass nor a \DateTime.
Option 3. is the current behavior but silently doing so results in a
type hint violation. Emitting an E_NOTICE
at this point is the most
sensible thing that we can do at this point in my opinion. Extending
this logic to all kind of properties is just logical to keep
conditionals in the internals low and have a consistent behavior across
all of the userland functionality. After all, aren't the following
things equal?
$a;
echo $a; // null + E_NOTICE
class O {
public int $x;
}
echo (new O)->x; // null + E_NOTICE
One could even argue that an E_NOTICE
is required for void routines too.
function f() {}
echo f(); // null + E_NOTICE
But that's another story. :)
--
Richard "Fleshgrinder" Fussenegger
The problem is a completely different one, how should the following code
behave?class A {
public int $x;
}
(new A)->x;
The property has no value assigned but it is being accessed. The current
PHP behavior is to simply initialize it with null. But this is
impossible according to the type definition.There are not many ways to handle this. I think we already had all of
them proposed:
- Fatal error after __construct was called.
- Fatal error and abort.
- Initialize with appropriate type.
- Initialize with null.
Under another 5th option, the problem you state does not arise. Disallow
"public int $x;". Under this option you may declare $x with type int and
an initial value or you may declare $x without type but you may not
declare $x with type (nullable or not) and undefined initial value.
Tom
- Fatal error after __construct was called.
- Fatal error and abort.
- Initialize with appropriate type.
- Initialize with null.
Under another 5th option, the problem you state does not arise. Disallow
"public int $x;". Under this option you may declare $x with type int and
an initial value or you may declare $x without type but you may not
declare $x with type (nullable or not) and undefined initial value.
That has the same problem as 2 - not all types can be initialised
statically, so can't be declared this way:
class Foo {
HTTPRequest $request = SOMETHING;
}
How do I safely initialise that?
Regards,
Rowan Collins
[IMSoP]
The problem is a completely different one, how should the following code
behave?class A {
public int $x;
}
(new A)->x;
The property has no value assigned but it is being accessed. The current
PHP behavior is to simply initialize it with null. But this is
impossible according to the type definition.There are not many ways to handle this. I think we already had all of
them proposed:
- Fatal error after __construct was called.
- Fatal error and abort.
- Initialize with appropriate type.
- Initialize with null.
Under another 5th option, the problem you state does not arise. Disallow
"public int $x;". Under this option you may declare $x with type int and
an initial value or you may declare $x without type but you may not
declare $x with type (nullable or not) and undefined initial value.Tom
This would be a valid approach too, yes. I personally would be against
it because I do not want to initialize all my properties.
class A {
private int $x;
public function getX() {
if (empty($this->x)) {
$this->x = 42;
}
return $this->x;
}
}
This would not yield an E_NOTICE
because both isset() and empty() never
do. This allows the attentive programmers to keep up there coding
practices without the necessity to assign meaningless values everywhere.
class A {
/** -1 is invalid */
public int $x = -1;
/** 'INVALID' is invalid but empty string is allowed */
public string $s = 'INVALID';
/** Null byte is invalid but anything else is valid */
public string $welcome_to_the_c_world = '\0';
}
Not cool. :(
--
Richard "Fleshgrinder" Fussenegger
Under another 5th option, the problem you state does not arise. Disallow
"public int $x;". Under this option you may declare $x with type int and
an initial value or you may declare $x without type but you may not
declare $x with type (nullable or not) and undefined initial value.Tom
This would be a valid approach too, yes. I personally would be against
it because I do not want to initialize all my properties.class A {
private int $x;
public function getX() {
if (empty($this->x)) {
$this->x = 42;
}
return $this->x;
}}
This would not yield an
E_NOTICE
because both isset() and empty() never
do. This allows the attentive programmers to keep up there coding
practices without the necessity to assign meaningless values everywhere.class A {
/** -1 is invalid */
public int $x = -1;/** 'INVALID' is invalid but empty string is allowed */
public string $s = 'INVALID';/** Null byte is invalid but anything else is valid */
public string $welcome_to_the_c_world = '\0';}
If you want that kind of thing, you can do it the old PHP way like this
class A {
private ?int $x = null;
...
Tom
This would be a valid approach too, yes. I personally would be against
it because I do not want to initialize all my properties.class A {
private int $x;
public function getX() {
if (empty($this->x)) {
$this->x = 42;
}
return $this->x;
}}
This would not yield an
E_NOTICE
because both isset() and empty() never
do. This allows the attentive programmers to keep up there coding
practices without the necessity to assign meaningless values everywhere.class A {
/** -1 is invalid */
public int $x = -1;/** 'INVALID' is invalid but empty string is allowed */
public string $s = 'INVALID';/** Null byte is invalid but anything else is valid */
public string $welcome_to_the_c_world = '\0';}
If you want that kind of thing, you can do it the old PHP way like this
class A {
private ?int $x = null;
...
Doing that defeats the whole purpose of typed properties in my opinion.
I want to be sure that nobody can assign null to this property and only
allow it to be null until I initialize it myself.
class A {
private int $x;
private function getX() {
if (empty($this->x)) {
$this->x = 42;
}
return $this->x;
}
public function doSomething() {
// lots of code ...
$_ = $__ * $this->x;
// lots of code ...
}
}
This would emit an E_NOTICE
(and possibly end up in an error due to
null) and the developer of the doSomething() method will notice that she
should call getX() instead.
This would not be the case with private ?int $x. :(
--
Richard "Fleshgrinder" Fussenegger
class O {
public int $x;
}
echo (new O)->x; // null +E_NOTICE
As I'm sure has already been pointed out (I haven't followed the whole
thread) this defeats a large advantage of typed properties - I now can't
read from the property without checking if it's null, so I can't do this:
class O {
public \DateTimeImmutable $d;
}
echo (new O)->d->format('Y-m-d H:i:s');
There's no ? on the type def, so I ought to be able to trust the type. A
TypeError makes more sense here, because it is a programming error for
it to be in this state.
Regards,
Rowan Collins
[IMSoP]
As I'm sure has already been pointed out (I haven't followed the whole
thread) this defeats a large advantage of typed properties - I now can't
read from the property without checking if it's null, so I can't do this:class O {
public \DateTimeImmutable $d;
}
echo (new O)->d->format('Y-m-d H:i:s');There's no ? on the type def, so I ought to be able to trust the type. A
TypeError makes more sense here, because it is a programming error for
it to be in this state.
That is exactly what Stanislav raised too. I really have no idea how we
should prevent this from happening and honestly think we shouldn't. Your
construct will result in a fatal error (also known as
NullPointerException to some, evil-laughter) anyways preceded by an
E_NOTICE
that tells you that your property is not defined.
I am of course open for ideas but emitting another kind of fatal error
does not really make things better imho.
--
Richard "Fleshgrinder" Fussenegger
As I'm sure has already been pointed out (I haven't followed the whole
thread) this defeats a large advantage of typed properties - I now can't
read from the property without checking if it's null, so I can't do this:class O {
public \DateTimeImmutable $d;
}
echo (new O)->d->format('Y-m-d H:i:s');There's no ? on the type def, so I ought to be able to trust the type. A
TypeError makes more sense here, because it is a programming error for
it to be in this state.That is exactly what Stanislav raised too. I really have no idea how we
should prevent this from happening and honestly think we shouldn't. Your
construct will result in a fatal error (also known as
NullPointerException to some, evil-laughter) anyways preceded by an
E_NOTICE
that tells you that your property is not defined.I am of course open for ideas but emitting another kind of fatal error
does not really make things better imho.
I think the difference is the emphasis of whose responsibility it is to
fix it: a TypeError confirms that the error is in the O class for
exposing an incorrectly typed property; a NullPointerException, as you
put it, makes it my fault for trusting the class.
Or to put it a different way, is the error in the first arrow (accessing
"->d") or the second one (de-referencing "d->").
At the end of the day, all the type notations being added to PHP are
just assertions anyway. So the same could be said of this:
function foo(\DateTime $d) {
echo $d->format('Y-m-d H:i:s');
}
foo(null);
If this didn't throw an error at "foo(null)", it would throw an error at
"$d->format".
Regards,
Rowan Collins
[IMSoP]
I think the difference is the emphasis of whose responsibility it is to
fix it: a TypeError confirms that the error is in the O class for
exposing an incorrectly typed property; a NullPointerException, as you
put it, makes it my fault for trusting the class.Or to put it a different way, is the error in the first arrow (accessing
"->d") or the second one (de-referencing "d->").At the end of the day, all the type notations being added to PHP are
just assertions anyway. So the same could be said of this:function foo(\DateTime $d) {
echo $d->format('Y-m-d H:i:s');
}
foo(null);If this didn't throw an error at "foo(null)", it would throw an error at
"$d->format".
Yes, they are just assertions and design by contract and you make a very
good point here for an actual error. I am convinced. ;)
However, it should not throw an error for isset() and empty() to allow
more special constructs. As we already have it in place everywhere with
the two.
--
Richard "Fleshgrinder" Fussenegger
I think the difference is the emphasis of whose responsibility it is to
fix it: a TypeError confirms that the error is in the O class for
exposing an incorrectly typed property; a NullPointerException, as you
put it, makes it my fault for trusting the class.Or to put it a different way, is the error in the first arrow (accessing
"->d") or the second one (de-referencing "d->").At the end of the day, all the type notations being added to PHP are
just assertions anyway. So the same could be said of this:function foo(\DateTime $d) {
echo $d->format('Y-m-d H:i:s');
}
foo(null);If this didn't throw an error at "foo(null)", it would throw an error at
"$d->format".Yes, they are just assertions and design by contract and you make a very
good point here for an actual error. I am convinced. ;)However, it should not throw an error for isset() and empty() to allow
more special constructs. As we already have it in place everywhere with
the two.
DateTime is probably a better example to work with than 'int' since it
already has a long pedigree of problems and clashes where many projects
had their own interpretation and still use their own version of the
class. My problem with '\DateTime $d' is that by default it returns
'now' rather than an 'empty/null' value in which to put the data from
the database. I can't create a 'null' version of DateTime so one has to
have a phantom date simply to ensure you know if it has been initialized
with your own data later - or continue to use the older user space classes.
It is handling just how the variable/property is initialized that is the
whole problem here and there may be very good reasons that the
initialization is later then the 'construct' be that a DateTime or an
int ... null is the correct way of indicating that 'as yet we do not
have the value of type xxx' just as it does for an untyped value, so
trying to make a special case to block it's use is simply strangling
normal usage in other cases?
--
Lester Caine - G8HFL
Contact - http://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - http://lsces.co.uk
EnquirySolve - http://enquirysolve.com/
Model Engineers Digital Workshop - http://medw.co.uk
Rainbow Digital Media - http://rainbowdigitalmedia.co.uk
DateTime is probably a better example to work with than 'int' since it
already has a long pedigree of problems and clashes where many projects
had their own interpretation and still use their own version of the
class. My problem with '\DateTime $d' is that by default it returns
'now' rather than an 'empty/null' value in which to put the data from
the database. I can't create a 'null' version of DateTime so one has to
have a phantom date simply to ensure you know if it has been initialized
with your own data later - or continue to use the older user space classes.It is handling just how the variable/property is initialized that is the
whole problem here and there may be very good reasons that the
initialization is later then the 'construct' be that a DateTime or an
int ... null is the correct way of indicating that 'as yet we do not
have the value of type xxx' just as it does for an untyped value, so
trying to make a special case to block it's use is simply strangling
normal usage in other cases?
Could you come up with some example code of what you cannot achieve? I
cannot think of a single situation that is valid and not a developer
error which would be impossible with the currently discussed approach.
--
Richard "Fleshgrinder" Fussenegger
DateTime is probably a better example to work with than 'int' since it
already has a long pedigree of problems and clashes where many projects
had their own interpretation and still use their own version of the
class. My problem with '\DateTime $d' is that by default it returns
'now' rather than an 'empty/null' value in which to put the data from
the database. I can't create a 'null' version of DateTime so one has to
have a phantom date simply to ensure you know if it has been initialized
with your own data later - or continue to use the older user space classes.It is handling just how the variable/property is initialized that is the
whole problem here and there may be very good reasons that the
initialization is later then the 'construct' be that a DateTime or an
int ... null is the correct way of indicating that 'as yet we do not
have the value of type xxx' just as it does for an untyped value, so
trying to make a special case to block it's use is simply strangling
normal usage in other cases?Could you come up with some example code of what you cannot achieve? I
cannot think of a single situation that is valid and not a developer
error which would be impossible with the currently discussed approach.
If I am creating an object which is a 'person' for which I actually want
to ensure I have valid dates for birth, marriage and death and an
integer 'no of siblings' then I have a set of 'typed' properties.
Initially the object is not initialized ... no person found in the
database ... so null values for every property in the object. When I
search and find a match, then I may get someone who is 'alive' so the
death property remains null. Currently one can not use DateTime
variables to do any of this as they have already defined their own
alternative to null by blocking it. So in this case it is pointless
trying to create any 'can be null' flag? Class variables are simply not
handling the problem consistently so one has to jump through hoops to
get back what used to be a simple null initialised set of properties?
--
Lester Caine - G8HFL
Contact - http://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - http://lsces.co.uk
EnquirySolve - http://enquirysolve.com/
Model Engineers Digital Workshop - http://medw.co.uk
Rainbow Digital Media - http://rainbowdigitalmedia.co.uk
If I am creating an object which is a 'person' for which I actually want
to ensure I have valid dates for birth, marriage and death and an
integer 'no of siblings' then I have a set of 'typed' properties.
Initially the object is not initialized ... no person found in the
database ... so null values for every property in the object. When I
search and find a match, then I may get someone who is 'alive' so the
death property remains null. Currently one can not use DateTime
variables to do any of this as they have already defined their own
alternative to null by blocking it. So in this case it is pointless
trying to create any 'can be null' flag? Class variables are simply not
handling the problem consistently so one has to jump through hoops to
get back what used to be a simple null initialised set of properties?
The current proposal (or the one which I am advocating) for that is that
you would have something like the following:
class Person {
public \DateTime $birth;
public ?\DateTime $marriage;
public ?\DateTime $death;
public int $numSiblings;
}
Creating an object would leave it in an invalid state:
$me = new Person;
// accessing $me->birth or $me->numSiblings is an error in this state,
because defaulting to NULL
would violate their type constraint
// accessing $me->marriage or $me->death returns NULL
(because they
allow nulls) but raises E_NOTICE
Now you can initialise the mandatory fields:
$me->birth = new \DateTime($db_record['birth_date']);
$me->marriage = new \DateTime($db_record['marriage_date']);
$me->death = new \DateTime($db_record['death_date']);
$me->numSiblings = $db_record['num_siblings'];
// all properties can now be safely accessed
// $me->marriage and $me->death may still be null, as indicated by the
"?" in their type spec
// $me->birth is guaranteed to be a \DateTime object
Regards,
--
Rowan Collins
[IMSoP]
The current proposal (or the one which I am advocating) for that is that
you would have something like the following:class Person {
public \DateTime $birth;
public ?\DateTime $marriage;
public ?\DateTime $death;
public int $numSiblings;
}Creating an object would leave it in an invalid state:
$me = new Person;
// accessing $me->birth or $me->numSiblings is an error in this state,
because defaulting toNULL
would violate their type constraint
// accessing $me->marriage or $me->death returnsNULL
(because they
allow nulls) but raisesE_NOTICE
Now you can initialise the mandatory fields:
$me->birth = new \DateTime($db_record['birth_date']);
$me->marriage = new \DateTime($db_record['marriage_date']);
$me->death = new \DateTime($db_record['death_date']);
$me->numSiblings = $db_record['num_siblings'];
// all properties can now be safely accessed
// $me->marriage and $me->death may still be null, as indicated by the
"?" in their type spec
// $me->birth is guaranteed to be a \DateTime objectRegards,
Nothing to add here. :)
PS: This needs a cast unless some special driver options are active.
$me->numSiblings = (int) $db_record['num_siblings'];
--
Richard "Fleshgrinder" Fussenegger
PS: This needs a cast unless some special driver options are active.
$me->numSiblings = (int) $db_record['num_siblings'];
...or unless strict_types=0, in which case PHP will add an implicit cast
for you. :)
Regards,
--
Rowan Collins
[IMSoP]
PS: This needs a cast unless some special driver options are active.
$me->numSiblings = (int) $db_record['num_siblings'];
...or unless strict_types=0, in which case PHP will add an implicit cast
for you. :)Regards,
I need to get used to that nice new feature. ;)
--
Richard "Fleshgrinder" Fussenegger
If I am creating an object which is a 'person' for which I actually want
to ensure I have valid dates for birth, marriage and death and an
integer 'no of siblings' then I have a set of 'typed' properties.
Initially the object is not initialized ... no person found in the
database ... so null values for every property in the object. When I
search and find a match, then I may get someone who is 'alive' so the
death property remains null. Currently one can not use DateTime
variables to do any of this as they have already defined their own
alternative to null by blocking it. So in this case it is pointless
trying to create any 'can be null' flag? Class variables are simply not
handling the problem consistently so one has to jump through hoops to
get back what used to be a simple null initialised set of properties?The current proposal (or the one which I am advocating) for that is that
you would have something like the following:class Person {
public \DateTime $birth;
public ?\DateTime $marriage;
public ?\DateTime $death;
public int $numSiblings;
}Creating an object would leave it in an invalid state:
$me = new Person;
// accessing $me->birth or $me->numSiblings is an error in this state,
because defaulting toNULL
would violate their type constraint
I think what is irritating here is that actually both of these can still
validly be null. It's HAVING to add the ? to every item when actually by
conventions 'NOT NULL' is the exception. Would ? indicating 'NOT NULL'
actually be better since it IS only adding that flag which is required
here to block the use of null?
// accessing $me->marriage or $me->death returns
NULL
(because they
allow nulls) but raisesE_NOTICE
BUT DateTime currently will not store 'null' - it returns 'now' instead.
We end up having to store string or integer values because we can't
store a null date :(
Now you can initialise the mandatory fields:
$me->birth = new \DateTime($db_record['birth_date']);
$me->marriage = new \DateTime($db_record['marriage_date']);
$me->death = new \DateTime($db_record['death_date']);
$me->numSiblings = $db_record['num_siblings'];
// all properties can now be safely accessed
// $me->marriage and $me->death may still be null, as indicated by the
"?" in their type spec
// $me->birth is guaranteed to be a \DateTime object
The suggestion I was seeing was that a 'NOT NULL' value had to be
initialized. I think my problem is perhaps is just what errors are
created that break the normal flow and what gets returned when checking
to see IF a value has been initialised.
$me->numSiblings would actually be null in this case if it has not yet
been established since the answer may well be '0' but that just explains
why null is still valid even for an int property. We just have no idea
if it's null because it's just been created or because the database read
returned 'null'.
The alternate 'not null' value is more clear cut in that 'null' can mean
that it is uninitialized, and one needs to run the initialization code
for it. I need is_null($me->numSiblings) to work and return true so I
can select the initialization code, or should there be a new state of
'uninit' as well? Then if we want to 'reset' the person object do the
'not null' entries get rest to null so they can be re-initialized.
'unset' the person does not remove the object simply rewinds it to an
unset state ... so should unset of these typed properties do that or do
we actually need an uninit?
--
Lester Caine - G8HFL
Contact - http://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - http://lsces.co.uk
EnquirySolve - http://enquirysolve.com/
Model Engineers Digital Workshop - http://medw.co.uk
Rainbow Digital Media - http://rainbowdigitalmedia.co.uk
I think what is irritating here is that actually both of these can still
validly be null. It's HAVING to add the ? to every item when actually by
conventions 'NOT NULL' is the exception. Would ? indicating 'NOT NULL'
actually be better since it IS only adding that flag which is required
here to block the use of null?
The use case for the majority is the opposite. Java decided to go for
the /everything can be null/ and people are making jokes about it all
the time.
BUT DateTime currently will not store 'null' - it returns 'now' instead.
We end up having to store string or integer values because we can't
store a null date :(
This is unrelated to typed properties. You can implement your own
DateTime class that has support for your special use case.
The suggestion I was seeing was that a 'NOT NULL' value had to be
initialized. I think my problem is perhaps is just what errors are
created that break the normal flow and what gets returned when checking
to see IF a value has been initialised.
Nothing breaks your normal flow, you will be able to use isset() and
empty() as you always were.
$me->numSiblings would actually be null in this case if it has not yet
been established since the answer may well be '0' but that just explains
why null is still valid even for an int property. We just have no idea
if it's null because it's just been created or because the database read
returned 'null'.
0 !== null and if your database can return null for this field add it to
its definition: public ?int $num_siblings
The alternate 'not null' value is more clear cut in that 'null' can mean
that it is uninitialized, and one needs to run the initialization code
for it. I need is_null($me->numSiblings) to work and return true so I
can select the initialization code, or should there be a new state of
'uninit' as well? Then if we want to 'reset' the person object do the
'not null' entries get rest to null so they can be re-initialized.
'unset' the person does not remove the object simply rewinds it to an
unset state ... so should unset of these typed properties do that or do
we actually need an uninit?
Not null would result in the same questions being raised as we are
facing them now.
You can use !isset() instead of is_null()
, it does the same thing
without errors being raised.
The concept of reseting objects and reinitializing them is something I
read very often from you. This is against all of OOP. What you are
basically doing is misusing classes as behaviorless anemic containers of
data. Even Smalltalk from the '70s did not encourage this and PHP never
did so either. Just because it is possible does not mean that it is the
proper way. Just make a new instance. ;)
--
Richard "Fleshgrinder" Fussenegger
The concept of reseting objects and reinitializing them is something I
read very often from you. This is against all of OOP. What you are
basically doing is misusing classes as behaviorless anemic containers of
data. Even Smalltalk from the '70s did not encourage this and PHP never
did so either. Just because it is possible does not mean that it is the
proper way. Just make a new instance. ;)
So every time I update the current tree because the client has selected
a different initial root I have to destroy perhaps 30 person objects and
rebuild the whole lot again from scratch? This is why I LIKED PHP when I
started using it in place of BCB(C++) ... I could build the array of
objects and then simply populate it from the database. 'Just make a new
instance' is a substantial overhead if the object has a large footprint?
Although with all this extra overhead 'delete and create' may be quicker
than 'load x'.
I do keep thinking that I should simply kick objects into touch and stay
with simple associative arrays using classes to manage the elements of
the array. I still need exactly the same type and constraint management
but rather than direct properties of the object they are simply an array
element. This is why I have such a problem with 'array' functions being
so different to 'properties' ... in my book they are the same thing!
--
Lester Caine - G8HFL
Contact - http://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - http://lsces.co.uk
EnquirySolve - http://enquirysolve.com/
Model Engineers Digital Workshop - http://medw.co.uk
Rainbow Digital Media - http://rainbowdigitalmedia.co.uk
So every time I update the current tree because the client has selected
a different initial root I have to destroy perhaps 30 person objects and
rebuild the whole lot again from scratch?
In a word, yes: if you have a different set of data, use a different set
of objects to hold it.
Objects are not memory slots to be re-filled, they represent a specific
instance of a thing. Creating and destroying 30 objects on a modern
system is the work of nanoseconds, and not something to be avoided at
all costs.
Regards,
--
Rowan Collins
[IMSoP]
So every time I update the current tree because the client has selected
a different initial root I have to destroy perhaps 30 person objects and
rebuild the whole lot again from scratch?In a word, yes: if you have a different set of data, use a different set
of objects to hold it.Objects are not memory slots to be re-filled, they represent a specific
instance of a thing. Creating and destroying 30 objects on a modern
system is the work of nanoseconds, and not something to be avoided at
all costs.
There we will have to disagree!
And much of the reworking of the core operations of PHP7 was to remove
the unnecessary transfer of data from one area of memory to another to
get the speed up. It is also in my book why PDO has not solved the
problem of handling data properly either.
Now it may be that completely stand alone PHP applications using nothing
else do require a different mindset, but once one adds using a database
as the persistent data layer some things are better done in other ways.
My example of genealogical data covers the vast majority of what is
needed to create any web based application and the problems that current
thinking in how PHP should evolve are creating.
I can run PHP to individually access every member of the family tree
with individual database reads ... or I can produce a CTE query that
returns all of the data in one hit. The results of that read are an
already populated associative array of data. In an ideal world every
element would be a fully typed and constrained variable and in early
PHP5 days the recommendations were to use hashes of variables rather
than individual 'var' properties as it made management more 'OO' like.
Now things seem to have swung full circle and hashes are out and we need
individual properties/variables again but of this new 'much more robust'
style? And strict typing is supposed to be better.
I get that 'var' needed to evolve to add accessibility and would ideally
benefit from a level of typing, but where ever that var is used should
work exactly the same way and one should be able to simply use a
$[id][date_of_birth] DateTime element as if it was simply a constrained
scalar variable. My major hate with genealogical data is having to make
sure that the dates and times are the same. Translating European
material which has been 'reformatted' to use American date style IS
something to be avoided at all costs and can add orders of magnitude to
processing time while storing the results as a simple integer eliminates
all of that processing. But even then one can't be sure if one is using
the same 'type' of second :(
Having accessed the data which naturally includes many null elements is
it REALLY more efficient then to create new copies of all of that material?
--
Lester Caine - G8HFL
Contact - http://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - http://lsces.co.uk
EnquirySolve - http://enquirysolve.com/
Model Engineers Digital Workshop - http://medw.co.uk
Rainbow Digital Media - http://rainbowdigitalmedia.co.uk
"Lester Caine" wrote in message news:5747F02A.2010808@lsces.co.uk...
So every time I update the current tree because the client has selected
a different initial root I have to destroy perhaps 30 person objects and
rebuild the whole lot again from scratch?In a word, yes: if you have a different set of data, use a different set
of objects to hold it.Objects are not memory slots to be re-filled, they represent a specific
instance of a thing.
I disagree completely. In a database system there is no rule that says an
object can only hold data from a single row otherwise Martin Fowler, the
author of PoEE, would not have patterns that hold data from several rows.
When I instantiate one of my table classes it is a valid but empty object.
There are two actions I can perform - inject data from the client via the
$_POST array, or tell it to retrieve data from the database. This may
involve just a single row, or multiple rows. When reading from the database
it may even result in zero rows.
You are trying to change the language to suit the way that you work without
considering the fact that your way is not the only way and this change may
not suit the way that others work. As far as I can see this proposal not
only does not solve a genuine problem, it adds levels of complexity that
will do nothing but increase the WTF! factor for untold numbers of
programmers who have been happily using the language for years.
--
Tony Marston
I disagree completely. In a database system there is no rule that says
an object can only hold data from a single row otherwise Martin Fowler,
the author of PoEE, would not have patterns that hold data from several
rows. When I instantiate one of my table classes it is a valid but empty
object. There are two actions I can perform - inject data from the
client via the $_POST array, or tell it to retrieve data from the
database. This may involve just a single row, or multiple rows. When
reading from the database it may even result in zero rows.
Yes?!? That is how everybody does it.
You are trying to change the language to suit the way that you work
without considering the fact that your way is not the only way and this
change may not suit the way that others work. As far as I can see this
proposal not only does not solve a genuine problem, it adds levels of
complexity that will do nothing but increase the WTF! factor for untold
numbers of programmers who have been happily using the language for years.
Oh come on, this story again? Please, just keep on using PHP in the way
you like it without making use of these new features. Nothing is
changing and if all your problems are solved, good. And please, please
don't start the PHP 5.2 was so much better discussion now and claiming
that we all are incapable programmers.
We know it by now. ;)
--
Richard "Fleshgrinder" Fussenegger
wrote in message
news:2049e48d-5dd3-051b-2564-afeb8c64ca48@fleshgrinder.com...
I disagree completely. In a database system there is no rule that says
an object can only hold data from a single row otherwise Martin Fowler,
the author of PoEAA, would not have patterns that hold data from several
rows. When I instantiate one of my table classes it is a valid but empty
object. There are two actions I can perform - inject data from the
client via the $_POST array, or tell it to retrieve data from the
database. This may involve just a single row, or multiple rows. When
reading from the database it may even result in zero rows.Yes?!? That is how everybody does it.
Except for those who insist on having a separate object for each row.
You are trying to change the language to suit the way that you work
without considering the fact that your way is not the only way and this
change may not suit the way that others work. As far as I can see this
proposal not only does not solve a genuine problem, it adds levels of
complexity that will do nothing but increase the WTF! factor for untold
numbers of programmers who have been happily using the language for
years.Oh come on, this story again?
Yes, again. I (and others) like the PHP language to be kept simple, and we
are clever enough to achieve complex things with simple code, not simple
things with complex code.
Please, just keep on using PHP in the way
you like it without making use of these new features. Nothing is
changing and if all your problems are solved, good. And please, please
don't start the PHP 5.2 was so much better discussion now
I have never said that PHP 5.2 was better. FYI I have upgraded to each new
version as it appears, and I am currently on 5.6. I am not using version 7
yet simply because my main application uses SQL Server, and Microsoft have
failed to deliver a driver for version 7. I don't use any of the new
features that keep being added (except for the datetime object) simply
because I have no use for them. Adding them to my code would not add value
to my code, so I don't bother. I don't need typed properties, so I will
never use them.
and claiming that we all are incapable programmers.
Where did I say that?
We know it by now. ;)
Yet you still carry on by adding complexity to the language instead of
simplicity.
--
Tony Marston
"Lester Caine" wrote in message news:5747F02A.2010808@lsces.co.uk...
So every time I update the current tree because the client has selected
a different initial root I have to destroy perhaps 30 person objects
and
rebuild the whole lot again from scratch?In a word, yes: if you have a different set of data, use a different set
of objects to hold it.Objects are not memory slots to be re-filled, they represent a specific
instance of a thing.I disagree completely. In a database system there is no rule that says
an object can only hold data from a single row otherwise Martin Fowler,
the author of PoEE, would not have patterns that hold data from several
rows. When I instantiate one of my table classes it is a valid but empty
object. There are two actions I can perform - inject data from the
client via the $_POST array, or tell it to retrieve data from the
database. This may involve just a single row, or multiple rows. When
reading from the database it may even result in zero rows.
That sounds like an object that represents the database table as an
entity, that's fine. What does it contain - how is the row data
represented? If it's just an array of arrays, then none of this
discussion is relevant; if it's an array of model objects, then those
objects must have properties, and those properties can be given type
annotations under this proposal.
My comment was in reaction to Lester expressing surprise at destroying
and re-creating objects when new data is fetched. To be clear, I'm
talking about this:
// Clear old data
$this->data = [];
// Re-populate from new DB result
foreach ( $db_resultset as $row ) {
$this->data[] = new SomeModel($row);
}
The most useful typehints are inside the SomeModel class, not the
table-level wrapper. This is true even if they're just dumb structs, and
all the behaviour is at the table level, because it presumably has
basically one field: array $data.
The alternative implied by Lester's comment seemed to be to somehow
re-use the entries in the data array by calling a magic "reset" method,
then populating them with new data. That's what I meant by "slots".
If I create a Person object representing "Lester Caine", it makes
absolutely no sense to go out of my way to recycle it and use it to
represent "Tony Marston"; that's a new value of the same type, so a new
instance of the same class.
Regards,
Rowan Collins
[IMSoP]
My comment was in reaction to Lester expressing surprise at destroying
and re-creating objects when new data is fetched. To be clear, I'm
talking about this:// Clear old data
$this->data = [];
// Re-populate from new DB result
foreach ( $db_resultset as $row ) {
$this->data[] = new SomeModel($row);
}The most useful typehints are inside the SomeModel class, not the
table-level wrapper. This is true even if they're just dumb structs, and
all the behaviour is at the table level, because it presumably has
basically one field: array $data.
Probably the only difference here is that '$row' in my result set is not
a dumb structure. It has type information, field lengths and potentially
even value constraints. So my table level wrapper can potentially have
the same typehints that you are then adding to SomeModel. All the
properties of SomeModel already exist and if typed properties are
working correctly in PHP $row can simply be accessed directly. This is
why in my book all of the debate about 'attributes' and everything else
is so tied up in the definition of making a simple variable a much more
flexible generic object.
--
Lester Caine - G8HFL
Contact - http://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - http://lsces.co.uk
EnquirySolve - http://enquirysolve.com/
Model Engineers Digital Workshop - http://medw.co.uk
Rainbow Digital Media - http://rainbowdigitalmedia.co.uk
My comment was in reaction to Lester expressing surprise at destroying
and re-creating objects when new data is fetched. To be clear, I'm
talking about this:// Clear old data
$this->data = [];
// Re-populate from new DB result
foreach ( $db_resultset as $row ) {
$this->data[] = new SomeModel($row);
}The most useful typehints are inside the SomeModel class, not the
table-level wrapper. This is true even if they're just dumb structs, and
all the behaviour is at the table level, because it presumably has
basically one field: array $data.Probably the only difference here is that '$row' in my result set is not
a dumb structure. It has type information, field lengths and potentially
even value constraints. So my table level wrapper can potentially have
the same typehints that you are then adding to SomeModel. All the
properties of SomeModel already exist and if typed properties are
working correctly in PHP $row can simply be accessed directly. This is
why in my book all of the debate about 'attributes' and everything else
is so tied up in the definition of making a simple variable a much more
flexible generic object.
The type hints belong to SomeModel because you want to insert the data
into SomeModel and not into your repository (Table).
class Person {
public int $id;
public DateTimeImmutable $created;
public DateTimeImmutable $changed;
public string $name;
public DateTimeImmutable $birth;
public ?DateTimeImmutable $marriage;
public ?DateTimeImmutable $death;
public int $number_of_siblings = 0;
}
class SomeModels {
public function findAll() {
foreach ($db->select('SELECT * FROM SomeModel
') as $row) {
$some_model = new SomeModel;
$some_model->id = $row['id'];
$some_model->created = new DateTimeImmutable($row['created']);
$some_model->changed = new DateTimeImmutable($row['changed']);
$some_model->name = $row['name'];
$some_model->birth = new DateTimeImmutable($row['birth']);
if (isset($row['marriage'])) {
$some_model->marriage = new DateTimeImmutable($row['marriage']);
}
if (isset($row['death'])) {
$some_model->death = new DateTimeImmutable($row['death']);
}
$some_model->number_of_siblings = $row['number_of_siblings'];
yield $some_model;
}
}
}
The Person object is however still behaviorless and anemic, throwing
type hints at public properties does not change that fact.
--
Richard "Fleshgrinder" Fussenegger
The most useful typehints are inside the SomeModel class, not the
table-level wrapper. This is true even if they're just dumb structs,
and
all the behaviour is at the table level, because it presumably has
basically one field: array $data.Probably the only difference here is that '$row' in my result set is
not
a dumb structure. It has type information, field lengths and
potentially
even value constraints.
Do you mean it "has" that type information because it's guaranteed by other logic? Or is $row already an object, in which case we're saying the same thing?
So my table level wrapper can potentially have
the same typehints that you are then adding to SomeModel.
An object holding 20 people has no "date of birth" property, so how can it have a typehint for it? An object representing one row of the table can, and that's what most of the examples here are talking about.
I think maybe we're just talking cross-purposes - my example was a clumsy way of saying that somewhere the raw data coming back from the DBMS driver gets converted to an appropriate object.
The main point was that that conversion doesn't need some pool of empty objects to "fill up" with data, it just creates a new object for each row in the result set.
--
Rowan Collins
[IMSoP]
The most useful typehints are inside the SomeModel class, not the
table-level wrapper. This is true even if they're just dumb structs,
and
all the behaviour is at the table level, because it presumably has
basically one field: array $data.Probably the only difference here is that '$row' in my result set is
not
a dumb structure. It has type information, field lengths and
potentially
even value constraints.Do you mean it "has" that type information because it's guaranteed by
other logic? Or is $row already an object, in which case we're saying
the same thing?
As there is no current way of adding type information to the elements of
an associative array one has to manipulate
http://adodb.org/dokuwiki/doku.php?id=v5:dictionary:adofieldobject to
obtain the missing information. ADOdb will also return the row as an
object
http://adodb.org/dokuwiki/doku.php?id=v5:reference:recordset:fetchobj
and I have been trying for 15+ years to make that work with the type
data we have always had here.
The important thing to note here is that rather than having a
pre-defined set of fields with pre-defined types, the result set can be
controlled by what is returned by the database! While a 'built in'
object would have to define every possible filed that might be used. I
know that we have had differences in the past on 'well defined data',
and I do accept that providing a fixed set of fields would be a
preferable way of working, but being able to take a flexible result set
and provide a clean fully typed object from it should not be difficult?
So my table level wrapper can potentially have
the same typehints that you are then adding to SomeModel.An object holding 20 people has no "date of birth" property, so how can
it have a typehint for it? An object representing one row of the table
can, and that's what most of the examples here are talking about.
My initial query will establish the tree of people available in the
database, and just what data fields are available. Some fields are
'required' such as 'dateofbirth' which are by definition a DateTime type
but wild card results will add additional fields.
I think maybe we're just talking cross-purposes - my example was a
clumsy way of saying that somewhere the raw data coming back from the
DBMS driver gets converted to an appropriate object.
ADOdb will return a fully formed object containing it's set of data if
we get the way of handling typed 'variables' sorted properly! The
'person' object simply becomes a database query return ... and no
secondary copying is needed.
The main point was that that conversion doesn't need some pool of empty
objects to "fill up" with data, it just creates a new object for each
row in the result set.
My 'empty pots' are more related to rendering the displayed page. I can
see where my terminology is perhaps wrong, and perhaps a different way
of handling things which in the past have been easy as associative
arrays with null elements. It's being able to pick up just what
validation is needed on the page for those empty inputs which needs more
than a null object? The empty pot has all of the type/attribute
information ...
--
Lester Caine - G8HFL
Contact - http://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - http://lsces.co.uk
EnquirySolve - http://enquirysolve.com/
Model Engineers Digital Workshop - http://medw.co.uk
Rainbow Digital Media - http://rainbowdigitalmedia.co.uk
// accessing $me->marriage or $me->death returns
NULL
(because theyallow nulls) but raises
E_NOTICE
BUT DateTime currently will not store 'null' - it returns 'now' instead.
We end up having to store string or integer values because we can't
store a null date :(
That's exactly what ?DateTime is for - "either DateTime or Null", just
like in a database.
In most type systems, there is no such thing as "a null date" - if it's
null, it's not a date, it's a null value.
Regards,
--
Rowan Collins
[IMSoP]
// accessing $me->marriage or $me->death returns
NULL
(because theyallow nulls) but raises
E_NOTICE
BUT DateTime currently will not store 'null' - it returns 'now' instead.
We end up having to store string or integer values because we can't
store a null date :(That's exactly what ?DateTime is for - "either DateTime or Null", just
like in a database.In most type systems, there is no such thing as "a null date" - if it's
null, it's not a date, it's a null value.
The exact question here then is in relation to just how one uses
'DateTime' in this situation? Or more accurately how one maintains the
'or Null' state when the type does not allow null itself?
--
Lester Caine - G8HFL
Contact - http://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - http://lsces.co.uk
EnquirySolve - http://enquirysolve.com/
Model Engineers Digital Workshop - http://medw.co.uk
Rainbow Digital Media - http://rainbowdigitalmedia.co.uk
// accessing $me->marriage or $me->death returns
NULL
(because theyallow nulls) but raises
E_NOTICE
BUT DateTime currently will not store 'null' - it returns 'now' instead.
We end up having to store string or integer values because we can't
store a null date :(That's exactly what ?DateTime is for - "either DateTime or Null", just
like in a database.In most type systems, there is no such thing as "a null date" - if it's
null, it's not a date, it's a null value.The exact question here then is in relation to just how one uses
'DateTime' in this situation? Or more accurately how one maintains the
'or Null' state when the type does not allow null itself?
I don't understand the question.
class Foo { ?DateTime $d }
$f = new Foo;
$f->d = null;
$f->d = new DateTime;
It's either a DateTime, or it's null.
Regards,
Rowan Collins
[IMSoP]
// accessing $me->marriage or $me->death returns
NULL
(because theyallow nulls) but raises
E_NOTICE
BUT DateTime currently will not store 'null' - it returns 'now'
instead.
We end up having to store string or integer values because we can't
store a null date :(That's exactly what ?DateTime is for - "either DateTime or Null", just
like in a database.In most type systems, there is no such thing as "a null date" - if it's
null, it's not a date, it's a null value.The exact question here then is in relation to just how one uses
'DateTime' in this situation? Or more accurately how one maintains the
'or Null' state when the type does not allow null itself?I don't understand the question.
class Foo { ?DateTime $d }
$f = new Foo;
$f->d = null;
$f->d = new DateTime;It's either a DateTime, or it's null.
Now I am confused, but it's probably because I'm looking at a simple
$f->d = new DateTime( $initial );
While you are looking at
If ( isset($initial) )
then $f->d = new DateTime( $initial );
else $f->d = null;
While the original code would have simply been
$f->d = $initial;
And in an ideal world, because $d has been typed as DateTime this would
magically run 'new DateTime($initial)' which is where I am stumbling
because I simply look at '?DateTime $d' as creating a DateTime object
where in fact $d is a different type of object which will then hold the
link to the real one. I am still looking for $d to be an object I can do
all of the checks on that are provided by int or DateTime as appropriate
... the object needs to exist before the 'new' in order to know if you
need the first or second type of $initial code ... or we make typed
properties only work one way or the other?
--
Lester Caine - G8HFL
Contact - http://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - http://lsces.co.uk
EnquirySolve - http://enquirysolve.com/
Model Engineers Digital Workshop - http://medw.co.uk
Rainbow Digital Media - http://rainbowdigitalmedia.co.uk
Now I am confused, but it's probably because I'm looking at a simple
$f->d = new DateTime( $initial );
While you are looking at
If ( isset($initial) )
then $f->d = new DateTime( $initial );
else $f->d = null;While the original code would have simply been
$f->d = $initial;
And in an ideal world, because $d has been typed as DateTime this would
magically run 'new DateTime($initial)' which is where I am stumbling
because I simply look at '?DateTime $d' as creating a DateTime object
where in fact $d is a different type of object which will then hold the
link to the real one. I am still looking for $d to be an object I can do
all of the checks on that are provided by int or DateTime as appropriate
... the object needs to exist before the 'new' in order to know if you
need the first or second type of $initial code ... or we make typed
properties only work one way or the other?
PHP does not automagically instantiate the DateTime object for you, this
is still your responsibility. The type hint on the property only
restricts what you are allowed to assign to the property, nothing else.
It is true that PHP will coerce the value automatically if you are in
weak mode but this is only true for scalar values and nothing else. Also
note that you might not want that while working with a database because
PHP might destroy your UNSIGNED BIGINTs without you noticing.
class O {
public ?DateTime $d;
}
if (isset($row['d'])) {
$o->d = new DateTime($row['d']);
}
No need to assign null since it already is null by default and the
nullable hint will not result in any errors upon accessing it.
PS: The UNSIGNED BIGINT thing is one of the reasons why I want
intersection and union types so that we can do the following:
class O {
private int|string $id;
}
And leave it to a more intelligent system to determine whether this can
be safely converted to an int or not (e.g. MySQLi).
--
Richard "Fleshgrinder" Fussenegger
Now I am confused, but it's probably because I'm looking at a simple
$f->d = new DateTime( $initial );
While you are looking at
If ( isset($initial) )
then $f->d = new DateTime( $initial );
else $f->d = null;While the original code would have simply been
$f->d = $initial;
And in an ideal world, because $d has been typed as DateTime this would
magically run 'new DateTime($initial)' which is where I am stumbling
because I simply look at '?DateTime $d' as creating a DateTime object
where in fact $d is a different type of object which will then hold the
link to the real one. I am still looking for $d to be an object I can do
all of the checks on that are provided by int or DateTime as appropriate
... the object needs to exist before the 'new' in order to know if you
need the first or second type of $initial code ... or we make typed
properties only work one way or the other?PHP does not automagically instantiate the DateTime object for you, this
is still your responsibility. The type hint on the property only
restricts what you are allowed to assign to the property, nothing else.
Hence the 'in an ideal world'! It is perhaps worth making clear that a
'typed property' element IS simply a holder for the pointer to the real
element while the untyped properties are the element themselves? The
differentiation of 'scalar' probably comes into play, but does that
result in untyped scalers having to have the overheads for adding the
type flags?
It is true that PHP will coerce the value automatically if you are in
weak mode but this is only true for scalar values and nothing else. Also
note that you might not want that while working with a database because
PHP might destroy your UNSIGNED BIGINTs without you noticing.
And 32bit builds mess things up as well ...
class O {
public ?DateTime $d;
}if (isset($row['d'])) {
$o->d = new DateTime($row['d']);
}No need to assign null since it already is null by default and the
nullable hint will not result in any errors upon accessing it.
With the proviso that $o is not used again ... currently all of my
object classes have a constructor for a blank object along with a loader
to 'initialize' the data. Empty objects such as 'father' can be
populated with data and saved, loaded with an existing record, or wiped
if incorrect associations existed ... allowing the correct data to be
added. AJAX allows things to happen within a page which in the past
would have been complete new page loads without any of the problems of
'cached' data?
PS: The UNSIGNED BIGINT thing is one of the reasons why I want
intersection and union types so that we can do the following:class O {
private int|string $id;
}And leave it to a more intelligent system to determine whether this can
be safely converted to an int or not (e.g. MySQLi).
And the same thing applies to me except in relation to 32 bit or 64 bit
integers. Many legacy systems still use 32bit id's while all of my new
systems use 64bit ones. So int32 and int64 is one requirement which int
does not allow. On the SomeModel example the type of ['id'] is provided
by ADOdb and I can check if I need to handle the problem or not. If the
$row['id'] was correctly typed a lot of additional checks would be
eliminated, but the safe way of passing these id's IS as a string rather
than an integer.
--
Lester Caine - G8HFL
Contact - http://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - http://lsces.co.uk
EnquirySolve - http://enquirysolve.com/
Model Engineers Digital Workshop - http://medw.co.uk
Rainbow Digital Media - http://rainbowdigitalmedia.co.uk
Hence the 'in an ideal world'! It is perhaps worth making clear that a
'typed property' element IS simply a holder for the pointer to the real
element while the untyped properties are the element themselves? The
differentiation of 'scalar' probably comes into play, but does that
result in untyped scalers having to have the overheads for adding the
type flags?
Nope, they both work exactly the same way. Typed properties just guard
the properties from assignment of values that are not typed hinted against.
I don't know what you mean with untyped scalars. Every variable always
has a type. It is up to you if you care about it or not.
And 32bit builds mess things up as well ...
YES!
With the proviso that $o is not used again ... currently all of my
object classes have a constructor for a blank object along with a loader
to 'initialize' the data. Empty objects such as 'father' can be
populated with data and saved, loaded with an existing record, or wiped
if incorrect associations existed ... allowing the correct data to be
added. AJAX allows things to happen within a page which in the past
would have been complete new page loads without any of the problems of
'cached' data?
Sounds like bad design to me where you always need to know when to call
what and when what was called but if it works for you. :)
--
Richard "Fleshgrinder" Fussenegger
Now I am confused, but it's probably because I'm looking at a simple
$f->d = new DateTime( $initial );
While you are looking at
If ( isset($initial) )
then $f->d = new DateTime( $initial );
else $f->d = null;While the original code would have simply been
$f->d = $initial;
And in an ideal world, because $d has been typed as DateTime this would
magically run 'new DateTime($initial)' which is where I am stumbling
because I simply look at '?DateTime $d' as creating a DateTime object
where in fact $d is a different type of object which will then hold the
link to the real one. I am still looking for $d to be an object I can do
all of the checks on that are provided by int or DateTime as appropriate
... the object needs to exist before the 'new' in order to know if you
need the first or second type of $initial code ... or we make typed
properties only work one way or the other?PHP does not automagically instantiate the DateTime object for you, this
is still your responsibility. The type hint on the property only
restricts what you are allowed to assign to the property, nothing else.If you want something to magically happen when setting a property, PHP has
a way to do this called magic methods. Think about this:
class Person {
protected ?DateTime $death;
public function setDeath($date) {
if (!empty($date) && is_string($date)) {
$this->death = new DateTime($date);
}
elseif ($date instanceof DateTime) {
$this->death = $date;
}
// else $date is empty, person has not died
}
public function set($prop, $val) {
if (method_exists($this, "set$prop")) {
$this->{"set$prop"}($val);
}
}
public function __get($prop) {
if (property_exists($this, $prop)) {
return $this->$prop;
}
}
}
$p = new Person();
$p->death = "2015-06-01";
var_dump($p->death); // class DateTime#1 (3) { ...
$p2 = new Person();
$p2->death = new DateTime("2016-01-02");
var_dump($p2->death); // class DateTime#1 (3) { ...
$p3 = new Person();
var_dump($p3->death); // NULL, $p3 is not dead
Note, all of this is possible currently except the typing of the property,
you could enforce this right now in exactly this way.
If however, you want an object that will provide a format function (as in
your example) whether or not it was initiated with a value, then you would
need to define your own class for that:
class MyDateTime {
protected $dt;
public function __construct($date) {
if (!empty($date)) {
$this->dt = new DateTime($date);
}
}
public function format($format) {
if (!empty($this->dt)) {
return $this->dt->format($format);
}
return '';
}
}
Now you can blindly initialize it, and call format on it, whether or not
the date is provided. This is all controllable in userland, there is no
reason to make the language do it for you, as its purely a design decision.
Now you can blindly initialize it, and call format on it, whether or not
the date is provided. This is all controllable in userland, there is no
reason to make the language do it for you, as its purely a design decision.
Which has been the main argument all along. And if I'm doing it all in
userland anyway I do it in the database read/write rather than
duplicating all of that in secondary copies ...
--
Lester Caine - G8HFL
Contact - http://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - http://lsces.co.uk
EnquirySolve - http://enquirysolve.com/
Model Engineers Digital Workshop - http://medw.co.uk
Rainbow Digital Media - http://rainbowdigitalmedia.co.uk
Hence the 'in an ideal world'! It is perhaps worth making clear that a
'typed property' element IS simply a holder for the pointer to the real
element while the untyped properties are the element themselves?
In database terms, a property - typehinted or not, scalar, whatever - is like a column. On a particular instance (= row) each property will have some particular value.
Consider how "father" would be modelled as a foreign key in the DB. If you don't know the father, you would insert a null, not reference a special row representing The NullFather. And if you discovered the father was incorrect, you would update the row to point at a new record, not change the name on the old father record - it is not the name that was incorrect, only the relationship.
So in OO you do the same: if you don't know the father, you store a null in that field. If you want to change the father, you point at a new object, you don't go in and edit the existing one.
$this->father = $newFather;
There's no "empty object" here, no need to "reset" the object which was incorrectly assigned to the father property, just a new association representing the corrected information.
--
Rowan Collins
[IMSoP]
if (isset($row['d'])) {
$o->d = new DateTime($row['d']);
}No need to assign null since it already is null by default and the
nullable hint will not result in any errors upon accessing it.
Actually, that's one of the points up for discussion: should accessing the property when no assignment has been made raise an E_NOTICE, so that there is a difference between "?int $foo" and "?int $foo = null".
Regards,
--
Rowan Collins
[IMSoP]
if (isset($row['d'])) {
$o->d = new DateTime($row['d']);
}No need to assign null since it already is null by default and the
nullable hint will not result in any errors upon accessing it.Actually, that's one of the points up for discussion: should accessing the property when no assignment has been made raise an E_NOTICE, so that there is a difference between "?int $foo" and "?int $foo = null".
Regards,
True, I forgot and I am in favor of E_NOTICE. This was actually part of
my super confusing long example.
--
Richard "Fleshgrinder" Fussenegger
if (isset($row['d'])) {
$o->d = new DateTime($row['d']);
}No need to assign null since it already is null by default and the
nullable hint will not result in any errors upon accessing it.Actually, that's one of the points up for discussion: should accessing
the property when no assignment has been made raise an E_NOTICE, so that
there is a difference between "?int $foo" and "?int $foo = null".Regards,
True, I forgot and I am in favor of E_NOTICE. This was actually part of
my super confusing long example.
Back peddling a bit and looking forward ...
Ideally people would prefer for OO convention that properties are all
protected and only accessed via magic getters and setters? As per Ryan's
example. So
class Person {
protected ?DateTime $death;
any access to the properties is contained within the class. I still
think here that ADDING the '?' to restore default actions is the wrong
way around, but can't see an easy way of flagging 'not null' instead?
I think that moving on from this where I have been using associative
arrays for properties, this new element of typing does not work, and
that I have to move back to the 'array' being the list of typed
properties in the class? Which requires that every used property is
defined, while the results set from a database lookup is more flexible
than that ... which is why the switch was TO associative arrays for
properties in the classes. We even have generic code for getting and
setting array elements and the array is protected.
Most of you here are of the opinion that everything must be defined and
typed beforehand for good coding practice and I do accept that - EXCEPT
having defined everything the null state becomes more important when
sections of that data is not available. In my working practice it is
better that at run time one only creates all the magic around every
property if those properties are actually returned in the result set.
This may be wrong, but if I have created a validated and constrained set
of data in the database read, there should be no need to further
validate it? And my 'empty objects' are the structure of types and
constraints I need to manage input of new data.
I know you do not like me mixing up discussions, but the 'attribute'
debate is tightly woven with this if the documentation is providing the
type flag. I have all of that in either docblock or SQL schema and so I
THINK what I am looking for is
class Person {
protected var $death; // var is intentional - my flag for a holder
$death->__type( DateTime, not_null = false );
To which one can add
$death->__limit( $birth, '120years' );
Where $birth now has to be the same type and '120years' gets magically
converted to a date offset :)
BASICALLY any hard coded handling of attributes can co-exist tidily with
the much more dynamic and flexible nature that PHP used to have! If you
want strict fully bolted down code simply use C/C++ in the first place?
--
Lester Caine - G8HFL
Contact - http://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - http://lsces.co.uk
EnquirySolve - http://enquirysolve.com/
Model Engineers Digital Workshop - http://medw.co.uk
Rainbow Digital Media - http://rainbowdigitalmedia.co.uk
class Person {
protected var $death; // var is intentional - my flag for a holder
$death->__type( DateTime, not_null = false );To which one can add
$death->__limit( $birth, '120years' );
Where $birth now has to be the same type and '120years' gets magically
converted to a date offset :)
Adding that 'var' seemed totally natural as I wrote it. I could not
understand why at the time, but having slept on it, the reason has is
obvious. 'DateTime' can't be 'null' and any attempt to set it to such
will give you 'now' instead. BUT we have demonstrated (hopefully) that
we do need null dates, so we need a container that can either be null or
a date ... sounds suspiciously like good old var?
Now access to 'var' needs an attribute which can be
'public,private,protected' .. . but also 'read only' ( I'll ignore
'write only' because in my book that is part of the access level -
public read - protected write? ). The key element here is that if var is
a 'read_only' DateTime we don't need all the crap of DateTimeImmutable
and Derick's attempt at adding 'mutability' to the original dateTime
class would probably not have existed? The key I think here is IF 'var'
has been initialized and that depends on - perhaps - if the type
not_null is true. const comes to mind here as well and a
const var DateTime $start; Would give you a fixed snapshot time
HOPEFULLY you already see where this is going ...
Attributes such as __type, __limit, __length, __enum apply to the var
and are then used to ensure that the created value is valid.
Just to complete the picture in my archaic working practice...
public var $root_person
$root_person->__type( Person, not_null = false );
$root_person->__init( id1 );
Would have a value of an associative array of the data for 'Person'
consisting of the list of fully typed and constrained 'var' items. The
CODE for Person would simply work on the pointer to that hash. Which is
why I think I'm getting mixed up over the difference between the array
view of a value and the object view. In PHP they used to be
interchangeable but now things like this RFC are making them something
different?
--
Lester Caine - G8HFL
Contact - http://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - http://lsces.co.uk
EnquirySolve - http://enquirysolve.com/
Model Engineers Digital Workshop - http://medw.co.uk
Rainbow Digital Media - http://rainbowdigitalmedia.co.uk
// accessing $me->marriage or $me->death returns
NULL
(because theyallow nulls) but raises
E_NOTICE
BUT DateTime currently will not store 'null' - it returns 'now'
instead.
We end up having to store string or integer values because we can't
store a null date :(That's exactly what ?DateTime is for - "either DateTime or Null", just
like in a database.In most type systems, there is no such thing as "a null date" - if it's
null, it's not a date, it's a null value.The exact question here then is in relation to just how one uses
'DateTime' in this situation? Or more accurately how one maintains the
'or Null' state when the type does not allow null itself?I don't understand the question.
class Foo { ?DateTime $d }
$f = new Foo;
$f->d = null;
$f->d = new DateTime;It's either a DateTime, or it's null.
ACK. However, an alternative might be:
class NullDateTime extends DateTime {...}
class Foo { DateTime $d }
$f = new Foo;
$f->d = new NullDateTime;
$f->d = new DateTime;
--
Christoph M. Becker