Hi internals,
I would like to present my idea for namespaces refactoring and provide an
RFC for them.
Currently in PHP namespaces are implemented as a prefix of class or
function name. Using ReflectionClass or ReflectionFunction we can retrieve
class/function name and its namespace-name. In both situations it's
implemented as substring operation on class/function name:
ReflectionClass::getNamespaceName():
https://github.com/php/php-src/blob/master/ext/reflection/php_reflection.c#L5283
ReflectionFunction::getNamespaceName():
https://github.com/php/php-src/blob/master/ext/reflection/php_reflection.c#L3427
In both we can see the result of method is part of name.
IMHO those namespaces should be implemented as a struct like zend_namespace
struct with flags and name and should not be a part of class/function name.
Such change could increase performance on those Reflection* methods but not
only. I can see a good usage of such struct in implementing new features
like class/function visibility modifiers like: private, protected,
package-private or internal.
Some time ago there was discussion about final-properties I have started
https://marc.info/?t=145979255800003&r=1&w=2
In this discussion there was also talk about private class and internal
methods http://marc.info/?l=php-internals&m=146005798902985&w=2 as we can
see in PR https://github.com/php/php-src/pull/947#discussion_r21542750
which is little outdated there is implementation of class modifiers, and
there is exactly the same thing I was using in my package-private
implementation which uses string subctring comparison as we can see here in
ZEND_API int zend_is_same_namespace(const char *fc, const char *sc)
https://github.com/php/php-src/pull/947/files#diff-5459151fc119e0faf6f26162999c0cfdR2411
IMHO those hack's are result of badly implemented namespaces and leaving
them as they are won't bring new features highly optimized against
execution performance, because of string comparison.
Reimplementing namespaces as a struct could bring optimization level breat
boost as it can simply comapre pointer to zend_namespace struct.
An advantage of struct coult bring us new features like internal namespaces
in near future simply by flags field in zend_namespace struct - in feature
there could be some cleaning of build in classes and functions and move
into internal system namespace eg. \PHP\DateTime or whatever - not
important right now.
Another adwantage is comparing namespaces in class/function range modifiers
like private,protected,package-private or whatever.
There could be even some kind of extension flag for namespace providing
protection of namespace used in some extension awoiding to missusage and
providing some extension-private class/function implementations which could
be protected and the user would get an error while implementing, extending
- private classes from extension or smth like that.
I can see also some unconsistencies in class/function usage in PHP we can
use A/B::class to retrieve whole-class name with namespace, there could be
also some magic const like A/B::ns and it won't need to substring class
name, maybe in a future ther would be need to provide such syntax for
functions? A/c::ns where A/c is function, right now there is only way to
pass function name into ReflectionFunction as a string, classes have
::class magic const and it's easy to refactor such code, eg. change
namespace name which could lead to errors in IDE where functions are in
namespaces! Because searching for whole function name with namespace can
exists in code only as a string.
For eg. code like that:
<?php
namespace A {
class B {}
function c() {}
}
namespace {
$cr = new ReflectionClass(A\B::class);
var_dump($cr->getName(), $cr->getNamespaceName());
$fr = new ReflectionFunction('A\c');
var_dump($fr->getName(), $fr->getNamespaceName());
}
Will execute string substringing for class/function getNamespaceName()
reflection method which is simply SLOW! Such information isn't even cached!
Implementing such struct as zend_namespace could bring namespaces registry
at run-time. Such namespaces could have wheir parents as pointers to other
structs like in tree so they could optimize memory usage for eg. having
1000 classes in Test namespace preserves right now 1000 zend_string's with
class name and 1000 * 4 chars("Test") in quite longer namespace names those
numbers could be significant! The same is with function names.
I now it's not an easy task because there are lot of source code lines
which use zend_class->name and same for function pointers.
Nowadays probably none of extension uses namespaces so it's not a real
problem right now to provide such change.
Thanks for reading,
regards,
Michał Brzuchalski
On Mon, Aug 8, 2016 at 1:08 PM, Michał Brzuchalski
michal@brzuchalski.com wrote:
Hi internals,
I would like to present my idea for namespaces refactoring and provide an
RFC for them.Currently in PHP namespaces are implemented as a prefix of class or
function name. Using ReflectionClass or ReflectionFunction we can retrieve
class/function name and its namespace-name. In both situations it's
implemented as substring operation on class/function name:ReflectionClass::getNamespaceName():
https://github.com/php/php-src/blob/master/ext/reflection/php_reflection.c#L5283
ReflectionFunction::getNamespaceName():
https://github.com/php/php-src/blob/master/ext/reflection/php_reflection.c#L3427In both we can see the result of method is part of name.
IMHO those namespaces should be implemented as a struct like zend_namespace
struct with flags and name and should not be a part of class/function name.
Such change could increase performance on those Reflection* methods but not
only. I can see a good usage of such struct in implementing new features
like class/function visibility modifiers like: private, protected,
package-private or internal.Some time ago there was discussion about final-properties I have started
https://marc.info/?t=145979255800003&r=1&w=2
In this discussion there was also talk about private class and internal
methods http://marc.info/?l=php-internals&m=146005798902985&w=2 as we can
see in PR https://github.com/php/php-src/pull/947#discussion_r21542750
which is little outdated there is implementation of class modifiers, and
there is exactly the same thing I was using in my package-private
implementation which uses string subctring comparison as we can see here in
ZEND_API int zend_is_same_namespace(const char *fc, const char *sc)
https://github.com/php/php-src/pull/947/files#diff-5459151fc119e0faf6f26162999c0cfdR2411IMHO those hack's are result of badly implemented namespaces and leaving
them as they are won't bring new features highly optimized against
execution performance, because of string comparison.
Reimplementing namespaces as a struct could bring optimization level breat
boost as it can simply comapre pointer to zend_namespace struct.An advantage of struct coult bring us new features like internal namespaces
in near future simply by flags field in zend_namespace struct - in feature
there could be some cleaning of build in classes and functions and move
into internal system namespace eg. \PHP\DateTime or whatever - not
important right now.Another adwantage is comparing namespaces in class/function range modifiers
like private,protected,package-private or whatever.There could be even some kind of extension flag for namespace providing
protection of namespace used in some extension awoiding to missusage and
providing some extension-private class/function implementations which could
be protected and the user would get an error while implementing, extending
- private classes from extension or smth like that.
I can see also some unconsistencies in class/function usage in PHP we can
use A/B::class to retrieve whole-class name with namespace, there could be
also some magic const like A/B::ns and it won't need to substring class
name, maybe in a future ther would be need to provide such syntax for
functions? A/c::ns where A/c is function, right now there is only way to
pass function name into ReflectionFunction as a string, classes have
::class magic const and it's easy to refactor such code, eg. change
namespace name which could lead to errors in IDE where functions are in
namespaces! Because searching for whole function name with namespace can
exists in code only as a string.For eg. code like that:
<?php
namespace A {
class B {}
function c() {}
}
namespace {
$cr = new ReflectionClass(A\B::class);
var_dump($cr->getName(), $cr->getNamespaceName());
$fr = new ReflectionFunction('A\c');
var_dump($fr->getName(), $fr->getNamespaceName());
}Will execute string substringing for class/function getNamespaceName()
reflection method which is simply SLOW! Such information isn't even cached!Implementing such struct as zend_namespace could bring namespaces registry
at run-time. Such namespaces could have wheir parents as pointers to other
structs like in tree so they could optimize memory usage for eg. having
1000 classes in Test namespace preserves right now 1000 zend_string's with
class name and 1000 * 4 chars("Test") in quite longer namespace names those
numbers could be significant! The same is with function names.I now it's not an easy task because there are lot of source code lines
which use zend_class->name and same for function pointers.Nowadays probably none of extension uses namespaces so it's not a real
problem right now to provide such change.
It is.
I agree with your thoughts.
Back in 2008, namespaces were designed and added to PHP 5.3 as just
substrings in class names.
That is not the right way to do things, but we had not that much time
to design them otherwise, and we did not want to break the API too
much by adding new structures.
So, namespaces were designed as "just" some substrings in class names,
with all the problems you spot.
I guess Dmitry will have some words about that, as he lead this
feature back in time.
Refactoring namespaces could happen, but many extensions use them, and
they are heavilly used as well in OPCache and its optimizer.
The task won't be easy, and the break will be massive for internal
(some extensions would need to be rewritten, as well as many parts of
the engine).
As such, you may target next PHP major, aka PHP 8 for your ideas.
Julien.P
Hi!
It is.
I agree with your thoughts.
Back in 2008, namespaces were designed and added to PHP 5.3 as just
substrings in class names.
That is correct (except that namespaces first appeared in 2007, first
commit f32ffe9b430b718628f868e449c1fcbdc8ec9ef6 if you're interested, of
course were many since then that changed stuff :). However, I do not
think what follows is.
That is not the right way to do things, but we had not that much time
to design them otherwise, and we did not want to break the API too
much by adding new structures.
So, namespaces were designed as "just" some substrings in class names,
with all the problems you spot.
Designing namespaces as they are was a deliberate design decision, and
it was not because we "didn't want to break APIs" or because we did not
know better or were afraid of adding another struct, but because it was
decided to choose a simple solution that allows to achieve a lot without
introducing a lot of complexity. I certainly object to the blanket
characterization of it as "not the right way to do things".
I took part in the implementation and though it was almost 9 years ago,
I think I still remember at least something :)
We did consider using something like Java or Python packages, and we
decided it's much too complex and not very suitable for dynamic world of
PHP where classes and class files can be loaded at any time at any order.
I think some people misunderstand this concept and think PHP namespaces
are like half-baked Java packages but without some features because we
were too short-sighted or too hasty to implement those. No, they are not
like Java packages and they are implemented as not being separate
entities but being name prefixes very consciously and deliberately.
Now, 9 years later, we can reopen this question and think maybe our
needs changed and maybe different model would serve us better. This is
certainly possible and there's nothing wrong with that. But I think we
need to approach it with right understanding of why we took certain
decisions and where these decisions come from. It is very wrong to
refactor something without understanding why it was built like that in
the first place, and without understanding the initial design and
requirements. We spent a real lot of time discussing all this stuff, and
it'll be a pity if we spend this time again without using our previous
experience.
I also don't see any need in having "namespace registry", and, in fact,
if you understand conceptual model of PHP namespaces as they are now, it
doesn't make much sense to me.
Stas Malyshev
smalyshev@gmail.com
The current approach to namespaces blocks new features like you see it
in the following PR. The link already points to the two most relevant
comments that should give you full context:
https://github.com/php/php-src/pull/947/#issuecomment-224912697
You might know other approaches to solve this. What I want to say is, we
should not re-discuss everything that was discussed before, we should
discuss the proposed features and refactor the namespaces IF it is required.
--
Richard "Fleshgrinder" Fussenegger
Hi!
The current approach to namespaces blocks new features like you see it
in the following PR. The link already points to the two most relevant
comments that should give you full context:https://github.com/php/php-src/pull/947/#issuecomment-224912697
I don't think this - private classes, etc. - is a good idea. It looks
like Javaization of the language and introducing more and more
complexity without any clear benefit, and AFAIK none of the mainstream
scripting languages have such concept. Anyway, this doesn't even have an
RFC, so I don't see why we should refactor everything to accommodate it.
But it is certainly does not fit the concept of namespaces. It needs
completely different packages concept - one that currently does not
exist on PHP. Trying to bolt it on existing concept is not going to do
any good. I think this is what it should start with - developing a good
concept, not trying to go from code modification to design. Design
should come first, then code modification.
You might know other approaches to solve this. What I want to say is, we
should not re-discuss everything that was discussed before, we should
discuss the proposed features and refactor the namespaces IF it is required.
Indeed.
Stas Malyshev
smalyshev@gmail.com
Hi Stas,
I'll answer your message as it directly refers to links with my name.
I understand the reasons of why namespaces were implemented that way, as I
follow this list for quite a long time. I never complained about the
implementation itself as it was publicly introduced originally back in
August 2008, when first PHP 5.3 alpha 1 got released.
Today I see 2 sides in PHP Internals. One that truly believes that PHP
should adopt more concepts of object orientation, such as Annotations,
Generics, Overloading, Class visibility, Collections, Exceptions, etc (the
side I support) and the other side that wants to make the engine more
robust, stable and faster, without entering in a rollercoaster of new
functionality to be introduced that brings instability.
One thing that didn't change is the constant increase of developers writing
and rewriting software using the language. Now these developers are also
improving their skills, and demanding more flexible ways of achieving their
needs. Language evolution should also be in compass with these new needs,
and that's where I think PHP is struggling to keep up, for a variety of
reasons.
Being a constant contributor to open source and many libraries that now
drives most of what people consider as "Modern PHP", I constantly face
problems with the language itself being unable to achieve my needs. When I
say my needs, I mean needs of heavily used libraries, with hundreds or
thousands of other projects depending on them.
I'll highlight the main driver of "private classes" conceptualization, in
an attempt to make this email short (which I already failed). I wrote a DAG
sorter as part of one of Doctrine packages. Many people started to
instantiate (and use this class, even though it was marked as an @internal)
and consume the benefits it provides, until I changed its API working on a
bug fix. If this class was never intended to be used outside of the scope
of this project, how can restrict its usage by outside packages? Feel free
to provide me answers or alternatives, and I'll happily agree with you that
private classes is a bad idea.
Namespace implementation is now the blocker of many new features that could
help solve complex problems that you don't see on a daily basis, but
library developers face daily. For example: allowing to assess
imported/used structs when declaring a class would drastically reduce the
complexity of Doctrine's annotation parser, which required token_get_all
and several logic to check for what you're including. Is there a better
solution for that too? Well, I'd argue Annotations has a market, and people
are just blind if they don't wanna see it. Answering that implementing
Annotations in userland is a bad idea is like telling a corporation
shouldn't create a product because you don't like it. Makes absolutely no
sense.
I do have a lengthy, detailed and complex plan of how namespace refactoring
could be made, what it would enable feature-wise, and how it would benefit
many projects. I'm open to share with you or any other internals developer,
assess impact over PHP source code and even help implementing it. I'm just
unable to do it all by myself due to my poor C knowledge. What I don't want
to see is blind resistance to language evolution and blaming over something
as badly designed without understanding the circumstances that drove to
that implementation decision.
Regards,
On Sat, Aug 13, 2016 at 3:20 PM, Stanislav Malyshev smalyshev@gmail.com
wrote:
Hi!
The current approach to namespaces blocks new features like you see it
in the following PR. The link already points to the two most relevant
comments that should give you full context:https://github.com/php/php-src/pull/947/#issuecomment-224912697
I don't think this - private classes, etc. - is a good idea. It looks
like Javaization of the language and introducing more and more
complexity without any clear benefit, and AFAIK none of the mainstream
scripting languages have such concept. Anyway, this doesn't even have an
RFC, so I don't see why we should refactor everything to accommodate it.But it is certainly does not fit the concept of namespaces. It needs
completely different packages concept - one that currently does not
exist on PHP. Trying to bolt it on existing concept is not going to do
any good. I think this is what it should start with - developing a good
concept, not trying to go from code modification to design. Design
should come first, then code modification.You might know other approaches to solve this. What I want to say is, we
should not re-discuss everything that was discussed before, we should
discuss the proposed features and refactor the namespaces IF it is
required.Indeed.
Stas Malyshev
smalyshev@gmail.com--
--
Guilherme Blanco
Lead Architect at E-Block
Hi!
Today I see 2 sides in PHP Internals. One that truly believes that PHP
should adopt more concepts of object orientation, such as Annotations,
Generics, Overloading, Class visibility, Collections, Exceptions, etc
Object orientation is a very broad term. You can be very well object
oriented language and not be Java. In fact, almost all OO languages are
not Java ;) And a lot of OO languages (most of ones I know) don't have
Javaesque features like private classes, many also do fine without
generics.
One thing that didn't change is the constant increase of developers
writing and rewriting software using the language. Now these developers
are also improving their skills, and demanding more flexible ways of
achieving their needs. Language evolution should also be in compass with
these new needs, and that's where I think PHP is struggling to keep up,
for a variety of reasons.
Please do not forget that PHP was designed as beginner's language with
low barrier of entry. Making EJB out of it goes contrary to that, and
introducing features that would serve 0.01% of very complex cases while
complicating the language model for 100% of the users does not seem to
be worth it to me.
Especially if in those 0.01% cases I don't see a lot of real
improvement, but rather a "good feeling" that you can write "really
strict" code even though there's nothing better in that code than if it
were written without all the complexities.
Being a constant contributor to open source and many libraries that now
drives most of what people consider as "Modern PHP", I constantly face
problems with the language itself being unable to achieve my needs. When
I say my needs, I mean needs of heavily used libraries, with hundreds or
thousands of other projects depending on them.
I would like to see what is impossible to implement in PHP without
private classes. I mean useful functionality, not circular reference "I
need private classes because I need classes to be private" of course.
What application can not be implemented without it? I have very hard
time imagining such application.
working on a bug fix. If this class was never intended to be used
outside of the scope of this project, how can restrict its usage by
outside packages? Feel free to provide me answers or alternatives, and
I'll happily agree with you that private classes is a bad idea.
You can document it and talk to your users. PHP is an open-source
software, with open code and open development model. If the code is out
there, people can use it. If they use it contrary to maintainer's
recommendations, they take a risk. But that's it.
Obviously, if the users used that class, it provided some functionality
they needed. If you removed that functionality, now the users don't have
it. It's not a problem that can be solved with access modifiers. In
fact, if there were access modifiers preventing users from using
necessary functionality, they'd probably fork the code and removed them.
Also, working around namespace-private classes is very easy - just put
your own code in the same namespace. Works for Java too IIRC.
Namespace implementation is now the blocker of many new features that
could help solve complex problems that you don't see on a daily basis,
but library developers face daily. For example: allowing to assess
imported/used structs when declaring a class would drastically reduce
the complexity of Doctrine's annotation parser, which required
token_get_all and several logic to check for what you're including. Is
This is a whole bunch of things here. First of all, annotation parser is
a solution for a problem that shouldn't exist - namely, lack of
annotation support. Second, the number of code that needs to pre-process
other PHP code in PHP is minuscule compared to code doing other things.
Third, I'm not even sure what "allowing to assess imported/used structs
when declaring a class" means - why Doctrine annotation parser declares
a class? What are "structs"? What use has to do with class declaration?
What does Doctrine parser needs to assess, actually? I'm confused.
Fourth, I very much question the assertion that this is the use case
library developers face daily. I've developed many libraries in my
career, and I never had to face this particular issue (unless, of
course, in my confusion - see above - I misunderstood what the issue is).
there a better solution for that too? Well, I'd argue Annotations has a
market, and people are just blind if they don't wanna see it. Answering
that implementing Annotations in userland is a bad idea is like telling
a corporation shouldn't create a product because you don't like it.
Not because I don't like it - but because this is not the right way
to do it. If you create a car with square wheels, I'd say it won't drive
well not because I hate squares but because I know round wheels drive
better. It's not a question of my personal aesthetic preferences.
I do have a lengthy, detailed and complex plan of how namespace
refactoring could be made, what it would enable feature-wise, and how it
Before getting to the question of how, we need to pass the question of
why. And that I think is much more important.
knowledge. What I don't want to see is blind resistance to language
evolution and blaming over something as badly designed without
understanding the circumstances that drove to that implementation decision.
Why do you assume this resistance is "blind"? Please do not fall into
temptation of imagining your opponents as incapable of reason. This
would not get us very far. No, there are very good reasons for
opposing complicating the language, copying Java features that make very
little sense in PHP context and overhauling the engine and introducing
massive complications to serve a tiny percentage of use cases, even
those probably produced by lack of a different feature (also lacking
because of rejection of simple solutions in a quest of building
complicated one that serves 0.01% of most complex cases).
--
Stas Malyshev
smalyshev@gmail.com
I do have a lengthy, detailed and complex plan of how namespace
refactoring could be made, what it would enable feature-wise, and how it
Before getting to the question of how, we need to pass the question of
why. And that I think is much more important.
On top of just how useful IS namespace and if it's needed 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
Hi!
Today I see 2 sides in PHP Internals. One that truly believes that PHP
should adopt more concepts of object orientation, such as Annotations,
Generics, Overloading, Class visibility, Collections, Exceptions, etcObject orientation is a very broad term. You can be very well object
oriented language and not be Java. In fact, almost all OO languages are
not Java ;) And a lot of OO languages (most of ones I know) don't have
Javaesque features like private classes, many also do fine without
generics.
This is actually not accurate:
- PHP 7 has private classes through anonymous/inner classes.
- C++ has private (and protected) classes through nesting and friend
classes. - C# and VB.NET have private classes and many more modifiers.
- Everything is private by default in Rust.
- Golang (another beginners language) has private and protected (internal).
- Swift has private and more classes.
- Ceylon has the shared annotation.
- Ruby has private classes through some tricks.
- ...
I stop here but you will find more.
Please do not forget that PHP was designed as beginner's language with
low barrier of entry. Making EJB out of it goes contrary to that, and
introducing features that would serve 0.01% of very complex cases while
complicating the language model for 100% of the users does not seem to
be worth it to me.
Especially if in those 0.01% cases I don't see a lot of real
improvement, but rather a "good feeling" that you can write "really
strict" code even though there's nothing better in that code than if it
were written without all the complexities.
I know that the numbers are figurative but I think that 80 to 20 might
be more accurate.
I would like to see what is impossible to implement in PHP without
private classes. I mean useful functionality, not circular reference "I
need private classes because I need classes to be private" of course.
What application can not be implemented without it? I have very hard
time imagining such application.
Impossible is such a strong word but lets consider for a second the
following example:
Create an entity and builder. The entity has to be immutable and
the builder is the only object that is allowed to create that
entity.
<?php
abstract class Data {
protected $id;
protected $name;
protected function __construct() {}
protected function __clone() {}
}
final class Entity extends Data {
public function getId() {
return $this->id;
}
public function getName() {
return $this->name;
}
}
final class Builder extends Data {
private $entity;
public function build() {
return clone $this->entity;
}
public function setId($id) {
$this->entity->id = $id;
}
public function setName($name) {
$this->entity->name = $name;
}
}
?>
The biggest problem is that we are leaking the abstract base class that
is required to allow the entity and builder access and thus sharing of
properties and methods. This is very bad because that class has no value
for users but its part of our public API. Hence, any change to it
requires special care AND it gives anyone extending that class access to
the properties as well.
Now consider the whole thing with friend classes or private classes and
its super easy to solve.
Another example that is not solvable with friend classes would be the
strategy pattern of hidden strategies as we find it for example in
Symfony's process component.
https://github.com/symfony/process/tree/master/Pipes
The pipes are purely internal to work around differences between PHP on
Unix and Windows. They are not meant for consumption through users, they
actually never face the user in any circumstance. Their only purpose is
it to abstract these differences between operating systems away from the
user to make it easier for them.
working on a bug fix. If this class was never intended to be used
outside of the scope of this project, how can restrict its usage by
outside packages? Feel free to provide me answers or alternatives, and
I'll happily agree with you that private classes is a bad idea.You can document it and talk to your users. PHP is an open-source
software, with open code and open development model. If the code is out
there, people can use it. If they use it contrary to maintainer's
recommendations, they take a risk. But that's it.
At trivago we have hundreds of applications, packages, libraries and
developers who work in a very fast moving environment. Reading
documentation is not something most people spend time with. This is sad
but nothing anyone can solve. Hence, they use stuff that they are not
supposed to use. Having more control over access would greatly help to
improve control over how other developers use a library.
Obviously, if the users used that class, it provided some functionality
they needed. If you removed that functionality, now the users don't have
it. It's not a problem that can be solved with access modifiers. In
fact, if there were access modifiers preventing users from using
necessary functionality, they'd probably fork the code and removed them.
Yes, and that is exactly what they should do. It would be best if they
contribute that code back and let the maintainers know about their use
case. Either its completely wrong what they do and one can help them to
do it the right way or the library really needs changing.
Also, working around namespace-private classes is very easy - just put
your own code in the same namespace. Works for Java too IIRC.
It's also easy to work around property and method access modifiers.
However, that is no argument to remove them now.
--
Richard "Fleshgrinder" Fussenegger
Hi!
- PHP 7 has private classes through anonymous/inner classes.
It's not exactly the same, and I suspect the same is true for Ruby. It's
true that anonymous classes can not be instantiated by other code. But
that is not what we were discussing here. This particular effect is
somewhat similar, the purpose of the feature is different.
Of course, some languages have them. Others don't. The point is saying
"it's not OO unless it implements $favorite_feature_of_mine" is not very
meaningful, at least at this point.
abstract class Data {
protected $id;
protected $name;
protected function __construct() {}
protected function __clone() {}
}final class Entity extends Data {
public function getId() {
return $this->id;
}
public function getName() {
return $this->name;
}
}final class Builder extends Data {
private $entity;
public function build() {
return clone $this->entity;
}
public function setId($id) {
$this->entity->id = $id;
}
public function setName($name) {
$this->entity->name = $name;
}
}?>
I'm afraid I don't understand this code. Why both entity and builder are
Data? It looks like combining factory object with actual data object.
This is why __construct/__clone exists - so the object could take care
of the details and the factory didn't have to know about it in details.
I presume you want to say that Data object should be private class. I
think - at least from my partial understanding of the code - that it
shouldn't exist at all, especially given you don't want to expose it to
the user.
Another example that is not solvable with friend classes would be the
strategy pattern of hidden strategies as we find it for example in
Symfony's process component.https://github.com/symfony/process/tree/master/Pipes
The pipes are purely internal to work around differences between PHP on
Unix and Windows. They are not meant for consumption through users, they
Sure, they aren't useful for most, but I see no problem in somebody
using them if necessary. Say, you may want to use Unix pipe on Windows
because you are in unix emulation environment, or you have a weird OS
that is not Unix and now Windows and want to reuse parts of these
classes to deal with it. There could be many cases we can't foresee when
we design it for our ideal use case.
At trivago we have hundreds of applications, packages, libraries and
developers who work in a very fast moving environment. Reading
documentation is not something most people spend time with.
I find so many things not right with this statement I don't know where
to start... So I just leave it with a note that having no time to do
things right usually means doing things wrong many times, and then
being forced to spend time to do things right because the whole jenga
tower crashes on your head. That is not a criticism of you or your
employer, but rather the result of experience learned the hard way. You
can get away with it from time to time, true, but it's just luck.
It's also easy to work around property and method access modifiers.
However, that is no argument to remove them now.
Not very easy. The only way to work around private is to use heavy
machinery like reflection. This is why, btw, private should be used very
carefully - when if you're sure nobody will need or is advised to
access that thing directly. Overuse of private, especially for methods,
leads to classes that are not extendable without horrendous amounts of
copypaste.
Also, there's a difference IMO between classes and class' properties.
Class details may be - indeed, need to be - hidden because this is what
the class is, one of it's reasons to exist - a tool to reduce complexity
by hiding irrelevant information and exposing only relevant information
as class' public API. You hide things not because you're afraid evil
users would abuse them - but as a service to the user to not overload
their mental models with irrelevant details and allow to deal with the
class by only considering the important things.
However, if we're talking about collection of classes sharing a
namespace, there's no such thing as "namespace's API", at least not in
common use. There's a concept of library's API, but it's not derived
from classes alone - it's usually what documentation does, while class
API can be yet figured from it's methods, library API is usually
impossible to figure out just looking at the classes - you need the next
level, documentation.
So in my opinion, if you hide some classes from instantiation from
outside namespace (which by itself carries a lot of assumptions, such as
every class that will ever need it will share the same namespace),
reduction in complexity is almost none, since nobody uses "list of
available classes in package/namespace" as a way to work with the
package anyway.
P.S. BTW I still don't know why you need to refactor namespaces to have
namespace-private classes anyway. Isn't it just a matter of the match
between current scope and target class name?
--
Stas Malyshev
smalyshev@gmail.com
Also, there's a difference IMO between classes and class' properties.
Class details may be - indeed, need to be - hidden because this is what
the class is, one of it's reasons to exist - a tool to reduce complexity
by hiding irrelevant information and exposing only relevant information
as class' public API. You hide things not because you're afraid evil
users would abuse them - but as a service to the user to not overload
their mental models with irrelevant details and allow to deal with the
class by only considering the important things.
If it were only about suppressing unimportant details, documentation
would suffice, e.g. /** @private */ or the leading underscore
convention. I am, however, quite happy to have language support to
catch inadvertent accesses.
--
Christoph M. Becker
Hi Stas,
I'll comment your PS, since I'm the author of the PR.
On Sun, Aug 14, 2016 at 6:11 PM, Stanislav Malyshev smalyshev@gmail.com
wrote:
Hi!
- PHP 7 has private classes through anonymous/inner classes.
It's not exactly the same, and I suspect the same is true for Ruby. It's
true that anonymous classes can not be instantiated by other code. But
that is not what we were discussing here. This particular effect is
somewhat similar, the purpose of the feature is different.Of course, some languages have them. Others don't. The point is saying
"it's not OO unless it implements $favorite_feature_of_mine" is not very
meaningful, at least at this point.abstract class Data {
protected $id;
protected $name;
protected function __construct() {}
protected function __clone() {}
}final class Entity extends Data {
public function getId() {
return $this->id;
}
public function getName() {
return $this->name;
}
}final class Builder extends Data {
private $entity;
public function build() {
return clone $this->entity;
}
public function setId($id) {
$this->entity->id = $id;
}
public function setName($name) {
$this->entity->name = $name;
}
}?>
I'm afraid I don't understand this code. Why both entity and builder are
Data? It looks like combining factory object with actual data object.
This is why __construct/__clone exists - so the object could take care
of the details and the factory didn't have to know about it in details.I presume you want to say that Data object should be private class. I
think - at least from my partial understanding of the code - that it
shouldn't exist at all, especially given you don't want to expose it to
the user.Another example that is not solvable with friend classes would be the
strategy pattern of hidden strategies as we find it for example in
Symfony's process component.https://github.com/symfony/process/tree/master/Pipes
The pipes are purely internal to work around differences between PHP on
Unix and Windows. They are not meant for consumption through users, theySure, they aren't useful for most, but I see no problem in somebody
using them if necessary. Say, you may want to use Unix pipe on Windows
because you are in unix emulation environment, or you have a weird OS
that is not Unix and now Windows and want to reuse parts of these
classes to deal with it. There could be many cases we can't foresee when
we design it for our ideal use case.At trivago we have hundreds of applications, packages, libraries and
developers who work in a very fast moving environment. Reading
documentation is not something most people spend time with.I find so many things not right with this statement I don't know where
to start... So I just leave it with a note that having no time to do
things right usually means doing things wrong many times, and then
being forced to spend time to do things right because the whole jenga
tower crashes on your head. That is not a criticism of you or your
employer, but rather the result of experience learned the hard way. You
can get away with it from time to time, true, but it's just luck.It's also easy to work around property and method access modifiers.
However, that is no argument to remove them now.Not very easy. The only way to work around private is to use heavy
machinery like reflection. This is why, btw, private should be used very
carefully - when if you're sure nobody will need or is advised to
access that thing directly. Overuse of private, especially for methods,
leads to classes that are not extendable without horrendous amounts of
copypaste.Also, there's a difference IMO between classes and class' properties.
Class details may be - indeed, need to be - hidden because this is what
the class is, one of it's reasons to exist - a tool to reduce complexity
by hiding irrelevant information and exposing only relevant information
as class' public API. You hide things not because you're afraid evil
users would abuse them - but as a service to the user to not overload
their mental models with irrelevant details and allow to deal with the
class by only considering the important things.However, if we're talking about collection of classes sharing a
namespace, there's no such thing as "namespace's API", at least not in
common use. There's a concept of library's API, but it's not derived
from classes alone - it's usually what documentation does, while class
API can be yet figured from it's methods, library API is usually
impossible to figure out just looking at the classes - you need the next
level, documentation.
So in my opinion, if you hide some classes from instantiation from
outside namespace (which by itself carries a lot of assumptions, such as
every class that will ever need it will share the same namespace),
reduction in complexity is almost none, since nobody uses "list of
available classes in package/namespace" as a way to work with the
package anyway.P.S. BTW I still don't know why you need to refactor namespaces to have
namespace-private classes anyway. Isn't it just a matter of the match
between current scope and target class name?
I wanted to come up with a solution before writing an RFC.
The problem that is not yet solved in that PR is the check of instantiation
without being scoped. Example:
<?php
namespace Foo;
$bar = new ThisIsAPrivateClass();
This should work... but this code:
<?php
$bar = new \Foo\ThisIsAPrivateClass();
Should break.
The problem is that opline have no scope context, so at both of them the
scope = NULL.
We could easily add a scope every time a namespace is declared and remove
when ended, but as Nikita highlighted (here:
https://github.com/php/php-src/pull/947#issuecomment-224934615 ), this is
not the correct solution.
I hit a dead end on that PR. Changing opline (or oparray) to support a new
struct _zend_namespace is over my internals knowledge. I'd love if someone
could take over and complete it. I can write the RFC while someone else
finalize the code.
--
Stas Malyshev
smalyshev@gmail.com--
--
Guilherme Blanco
Lead Architect at E-Block
Hi!
- PHP 7 has private classes through anonymous/inner classes.
It's not exactly the same, and I suspect the same is true for Ruby. It's
true that anonymous classes can not be instantiated by other code. But
that is not what we were discussing here. This particular effect is
somewhat similar, the purpose of the feature is different.
It is part of what we are discussing.
Of course, some languages have them. Others don't. The point is saying
"it's not OO unless it implements $favorite_feature_of_mine" is not very
meaningful, at least at this point.
I never said that I agree with that particular comment since you are
completely right that this is not true. Visibility modifiers do not
require OO and OO does not require visibility modifiers.
abstract class Data {
protected $id;
protected $name;
protected function __construct() {}
protected function __clone() {}
}final class Entity extends Data {
public function getId() {
return $this->id;
}
public function getName() {
return $this->name;
}
}final class Builder extends Data {
private $entity;
public function build() {
return clone $this->entity;
}
public function setId($id) {
$this->entity->id = $id;
}
public function setName($name) {
$this->entity->name = $name;
}
}?>
I'm afraid I don't understand this code. Why both entity and builder are
Data? It looks like combining factory object with actual data object.
This is why __construct/__clone exists - so the object could take care
of the details and the factory didn't have to know about it in details.I presume you want to say that Data object should be private class. I
think - at least from my partial understanding of the code - that it
shouldn't exist at all, especially given you don't want to expose it to
the user.
You need to imagine that the entity has more properties of course and
no, the data class should not exist at all since it is only used to
emulate something that is currently simply impossible: allow some class
access to parts of another class while not allowing anyone else access
to it.
Again our goal:
Create a builder that builds an immutable entity.
A possible solution as I could imagine it:
<?php
// This is the magic that enabled backwards compatibility and changes
// semantics of leaving the visibility modifier out.
(assembly|group|module|package|...) Fleshgrinder\Internals;
namespace Examples\Builder;
public final class Entity {
//const class = 'Fleshgrinder\Internals\Examples\Builder\Entity';
number $id;
string $name;
DateTimeImmutable $created;
DateTimeImmutable $changed;
SomeValueObject1 $some_property_1;
SomeValueObject2 $some_property_2;
// ...
SomeValueObjectn $some_property_n;
function __construct() {}
function __clone() {}
// Getters for everything ...
}
public final class EntityBuilder {
private Entity $entity;
public function __construct() {
$this->entity = new Entity;
}
public function build() {
return clone $this->entity;
}
// Setters for everything ...
// Other useful stuff while building ...
}
?>
Currently the only way to achieve this is through an interface for the
entity and hoping that everyone uses that interface and never finds out
that the class actually offers much more functionality. Or you add a
shit load of arguments to the constructors. Or, my preferred approach,
via the magic __set_state method that is not auto-completed via the IDE.
That approach is still not ideal but one of the best. The problem is
users who find out that that __set_state method exists and they might
start using it. The last resort are hacks ... more about that later.
Another example that is not solvable with friend classes would be the
strategy pattern of hidden strategies as we find it for example in
Symfony's process component.https://github.com/symfony/process/tree/master/Pipes
The pipes are purely internal to work around differences between PHP on
Unix and Windows. They are not meant for consumption through users, theySure, they aren't useful for most, but I see no problem in somebody
using them if necessary. Say, you may want to use Unix pipe on Windows
because you are in unix emulation environment, or you have a weird OS
that is not Unix and now Windows and want to reuse parts of these
classes to deal with it. There could be many cases we can't foresee when
we design it for our ideal use case.
The joke is, you cannot do that. Both the Process and the ProcessBuilder
do not allow you to change the Pipes that are in use. Unless you hack it.
https://github.com/symfony/process/blob/master/Process.php
https://github.com/symfony/process/blob/master/ProcessBuilder.php
These are purely internal classes that serve a single purpose.
At trivago we have hundreds of applications, packages, libraries and
developers who work in a very fast moving environment. Reading
documentation is not something most people spend time with.I find so many things not right with this statement I don't know where
to start... So I just leave it with a note that having no time to do
things right usually means doing things wrong many times, and then
being forced to spend time to do things right because the whole jenga
tower crashes on your head. That is not a criticism of you or your
employer, but rather the result of experience learned the hard way. You
can get away with it from time to time, true, but it's just luck.
Thoughtful API design is bad? Pretty much any- and everything in PHP is
a breaking change because many things cannot be restricted. The addition
of visibility modifiers to constants in PHP 7.1 is a blessing because as
of now any change to a constant is directly a breaking change. This
sucks and only helps to increment the MAJOR part of our SemVer version
strings. Of course it also means that we need to touch a bunch of
packages because our composer is restricted to ^1.0 and now needs to be
^2.0.
It's also easy to work around property and method access modifiers.
However, that is no argument to remove them now.Not very easy. The only way to work around private is to use heavy
machinery like reflection. This is why, btw, private should be used very
carefully - when if you're sure nobody will need or is advised to
access that thing directly. Overuse of private, especially for methods,
leads to classes that are not extendable without horrendous amounts of
copypaste.
This is not true, closures are a lightweight work around here.
Nonetheless, heavy usage of private for methods does not make much sense
in any context or programming language but it does for any other kind of
member (we are left with constants and properties).
Also, there's a difference IMO between classes and class' properties.
Class details may be - indeed, need to be - hidden because this is what
the class is, one of it's reasons to exist - a tool to reduce complexity
by hiding irrelevant information and exposing only relevant information
as class' public API. You hide things not because you're afraid evil
users would abuse them - but as a service to the user to not overload
their mental models with irrelevant details and allow to deal with the
class by only considering the important things.
Now exchange class with package in the above paragraph and all of this
is equally true.
However, if we're talking about collection of classes sharing a
namespace, there's no such thing as "namespace's API", at least not in
common use. There's a concept of library's API, but it's not derived
from classes alone - it's usually what documentation does, while class
API can be yet figured from it's methods, library API is usually
impossible to figure out just looking at the classes - you need the next
level, documentation.
So in my opinion, if you hide some classes from instantiation from
outside namespace (which by itself carries a lot of assumptions, such as
every class that will ever need it will share the same namespace),
reduction in complexity is almost none, since nobody uses "list of
available classes in package/namespace" as a way to work with the
package anyway.
A namespace might not have any relevance for you but it has to the PHP
community at large who are using PSR-0/4 and Composer. All of my
namespaces start with Fleshgrinder and all of trivago's namespaces start
with Trivago. The same is true for Symfony, Drupal, Laravel, Zend, ...
so yes, my classes share the same namespace and they will always share
the same namespace unless someone else starts using my namespace.
Also your last paragraph is completely untrue. Every time one interacts
with the IDE auto-completion a listing of all available declarations
that fit the context is given. Hence, if I type new
followed by a
Ctrl + Space everything is being listed. The only thing I can do now is
adding a filter by starting to type a few characters.
Your approach of making everything public from the very beginning means
that you add everything to your public API. It also means that you need
to support everything in a backwards compatible manner. What does that
mean for us as library developers?
Traits are the worst since we cannot change anything.
Interfaces cannot be changed in any way other than moving members up
into another interface that we extend but the other interface is not
allowed to add any other members to our interface.
Classes cannot be changed in any way because someone might extend them.
We cannot introduce new members since extending classes might already
have them. We can however introduce new private things and fiddle around
with magic methods to some extend.
Final classes give one more freedom but are a pain for people who like
to rely on mocking and learned what interface explosion means.
It is about API design and not only about class API design. It is a
combination of documentation and modifiers.
P.S. BTW I still don't know why you need to refactor namespaces to have
namespace-private classes anyway. Isn't it just a matter of the match
between current scope and target class name?
This is better explained by nikic and others.
--
Richard "Fleshgrinder" Fussenegger
Hi,
As the initiator of this discussion, I would like to summarize it well, but
I need this a little more time.
Discussion was lively and I would like all the arguments to be included in
the summary.
It seems to me that I would need this help, and I am eager to progress and
evolution of language PHP.
Do any of you would be willing to support me mentally to create this
summary?
regards,
Michał Brzuchalski
2016-08-15 21:00 GMT+02:00 Fleshgrinder php@fleshgrinder.com:
Hi!
- PHP 7 has private classes through anonymous/inner classes.
It's not exactly the same, and I suspect the same is true for Ruby. It's
true that anonymous classes can not be instantiated by other code. But
that is not what we were discussing here. This particular effect is
somewhat similar, the purpose of the feature is different.It is part of what we are discussing.
Of course, some languages have them. Others don't. The point is saying
"it's not OO unless it implements $favorite_feature_of_mine" is not very
meaningful, at least at this point.I never said that I agree with that particular comment since you are
completely right that this is not true. Visibility modifiers do not
require OO and OO does not require visibility modifiers.abstract class Data {
protected $id;
protected $name;
protected function __construct() {}
protected function __clone() {}
}final class Entity extends Data {
public function getId() {
return $this->id;
}
public function getName() {
return $this->name;
}
}final class Builder extends Data {
private $entity;
public function build() {
return clone $this->entity;
}
public function setId($id) {
$this->entity->id = $id;
}
public function setName($name) {
$this->entity->name = $name;
}
}?>
I'm afraid I don't understand this code. Why both entity and builder are
Data? It looks like combining factory object with actual data object.
This is why __construct/__clone exists - so the object could take care
of the details and the factory didn't have to know about it in details.I presume you want to say that Data object should be private class. I
think - at least from my partial understanding of the code - that it
shouldn't exist at all, especially given you don't want to expose it to
the user.You need to imagine that the entity has more properties of course and
no, the data class should not exist at all since it is only used to
emulate something that is currently simply impossible: allow some class
access to parts of another class while not allowing anyone else access
to it.Again our goal:
Create a builder that builds an immutable entity.
A possible solution as I could imagine it:
<?php
// This is the magic that enabled backwards compatibility and changes
// semantics of leaving the visibility modifier out.
(assembly|group|module|package|...) Fleshgrinder\Internals;namespace Examples\Builder;
public final class Entity {
//const class = 'Fleshgrinder\Internals\Examples\Builder\Entity'; number $id; string $name; DateTimeImmutable $created; DateTimeImmutable $changed; SomeValueObject1 $some_property_1; SomeValueObject2 $some_property_2; // ... SomeValueObjectn $some_property_n; function __construct() {} function __clone() {} // Getters for everything ...
}
public final class EntityBuilder {
private Entity $entity; public function __construct() { $this->entity = new Entity; } public function build() { return clone $this->entity; } // Setters for everything ... // Other useful stuff while building ...
}
?>
Currently the only way to achieve this is through an interface for the
entity and hoping that everyone uses that interface and never finds out
that the class actually offers much more functionality. Or you add a
shit load of arguments to the constructors. Or, my preferred approach,
via the magic __set_state method that is not auto-completed via the IDE.
That approach is still not ideal but one of the best. The problem is
users who find out that that __set_state method exists and they might
start using it. The last resort are hacks ... more about that later.Another example that is not solvable with friend classes would be the
strategy pattern of hidden strategies as we find it for example in
Symfony's process component.https://github.com/symfony/process/tree/master/Pipes
The pipes are purely internal to work around differences between PHP on
Unix and Windows. They are not meant for consumption through users, theySure, they aren't useful for most, but I see no problem in somebody
using them if necessary. Say, you may want to use Unix pipe on Windows
because you are in unix emulation environment, or you have a weird OS
that is not Unix and now Windows and want to reuse parts of these
classes to deal with it. There could be many cases we can't foresee when
we design it for our ideal use case.The joke is, you cannot do that. Both the Process and the ProcessBuilder
do not allow you to change the Pipes that are in use. Unless you hack it.https://github.com/symfony/process/blob/master/Process.php
https://github.com/symfony/process/blob/master/ProcessBuilder.phpThese are purely internal classes that serve a single purpose.
At trivago we have hundreds of applications, packages, libraries and
developers who work in a very fast moving environment. Reading
documentation is not something most people spend time with.I find so many things not right with this statement I don't know where
to start... So I just leave it with a note that having no time to do
things right usually means doing things wrong many times, and then
being forced to spend time to do things right because the whole jenga
tower crashes on your head. That is not a criticism of you or your
employer, but rather the result of experience learned the hard way. You
can get away with it from time to time, true, but it's just luck.Thoughtful API design is bad? Pretty much any- and everything in PHP is
a breaking change because many things cannot be restricted. The addition
of visibility modifiers to constants in PHP 7.1 is a blessing because as
of now any change to a constant is directly a breaking change. This
sucks and only helps to increment the MAJOR part of our SemVer version
strings. Of course it also means that we need to touch a bunch of
packages because our composer is restricted to ^1.0 and now needs to be
^2.0.It's also easy to work around property and method access modifiers.
However, that is no argument to remove them now.Not very easy. The only way to work around private is to use heavy
machinery like reflection. This is why, btw, private should be used very
carefully - when if you're sure nobody will need or is advised to
access that thing directly. Overuse of private, especially for methods,
leads to classes that are not extendable without horrendous amounts of
copypaste.This is not true, closures are a lightweight work around here.
Nonetheless, heavy usage of private for methods does not make much sense
in any context or programming language but it does for any other kind of
member (we are left with constants and properties).Also, there's a difference IMO between classes and class' properties.
Class details may be - indeed, need to be - hidden because this is what
the class is, one of it's reasons to exist - a tool to reduce complexity
by hiding irrelevant information and exposing only relevant information
as class' public API. You hide things not because you're afraid evil
users would abuse them - but as a service to the user to not overload
their mental models with irrelevant details and allow to deal with the
class by only considering the important things.Now exchange class with package in the above paragraph and all of this
is equally true.However, if we're talking about collection of classes sharing a
namespace, there's no such thing as "namespace's API", at least not in
common use. There's a concept of library's API, but it's not derived
from classes alone - it's usually what documentation does, while class
API can be yet figured from it's methods, library API is usually
impossible to figure out just looking at the classes - you need the next
level, documentation.
So in my opinion, if you hide some classes from instantiation from
outside namespace (which by itself carries a lot of assumptions, such as
every class that will ever need it will share the same namespace),
reduction in complexity is almost none, since nobody uses "list of
available classes in package/namespace" as a way to work with the
package anyway.A namespace might not have any relevance for you but it has to the PHP
community at large who are using PSR-0/4 and Composer. All of my
namespaces start with Fleshgrinder and all of trivago's namespaces start
with Trivago. The same is true for Symfony, Drupal, Laravel, Zend, ...
so yes, my classes share the same namespace and they will always share
the same namespace unless someone else starts using my namespace.Also your last paragraph is completely untrue. Every time one interacts
with the IDE auto-completion a listing of all available declarations
that fit the context is given. Hence, if I typenew
followed by a
Ctrl + Space everything is being listed. The only thing I can do now is
adding a filter by starting to type a few characters.Your approach of making everything public from the very beginning means
that you add everything to your public API. It also means that you need
to support everything in a backwards compatible manner. What does that
mean for us as library developers?Traits are the worst since we cannot change anything.
Interfaces cannot be changed in any way other than moving members up
into another interface that we extend but the other interface is not
allowed to add any other members to our interface.Classes cannot be changed in any way because someone might extend them.
We cannot introduce new members since extending classes might already
have them. We can however introduce new private things and fiddle around
with magic methods to some extend.Final classes give one more freedom but are a pain for people who like
to rely on mocking and learned what interface explosion means.It is about API design and not only about class API design. It is a
combination of documentation and modifiers.P.S. BTW I still don't know why you need to refactor namespaces to have
namespace-private classes anyway. Isn't it just a matter of the match
between current scope and target class name?This is better explained by nikic and others.
--
Richard "Fleshgrinder" Fussenegger
--
pozdrawiam
Michał Brzuchalski
Hi Stas,
Answers inline.
On Sun, Aug 14, 2016 at 5:14 AM, Stanislav Malyshev smalyshev@gmail.com
wrote:
Hi!
Today I see 2 sides in PHP Internals. One that truly believes that PHP
should adopt more concepts of object orientation, such as Annotations,
Generics, Overloading, Class visibility, Collections, Exceptions, etcObject orientation is a very broad term. You can be very well object
oriented language and not be Java. In fact, almost all OO languages are
not Java ;) And a lot of OO languages (most of ones I know) don't have
Javaesque features like private classes, many also do fine without
generics.
I agree that we shouldn't copy and paste features as is. However, whenever
a need is found and there are common agreement over the existence of a
feature, we need to understand the decisions over each one and how are they
valid for PHP.
I'll draw 2 simple examples:
1- Generics: They solve a very interesting problem that is highlighted by
the usage of strict types.
It also hardens the implementation code, allowing to write more robust and
reliable libraries that could be used by others.
However, should it be a blind copy of Java generics? Of course no. In the
context of PHP, lower bounds, lower bounded wildcards, unbounded wildcards
and upper bounded wildcards make absolutely no sense.
Now let's suppose people decide to implement polymorphism in PHP and
everyone agrees with it. All of a sudden, lower bounded wildcards would
become a necessity.
One thing that didn't change is the constant increase of developers
writing and rewriting software using the language. Now these developers
are also improving their skills, and demanding more flexible ways of
achieving their needs. Language evolution should also be in compass with
these new needs, and that's where I think PHP is struggling to keep up,
for a variety of reasons.Please do not forget that PHP was designed as beginner's language with
low barrier of entry. Making EJB out of it goes contrary to that, and
introducing features that would serve 0.01% of very complex cases while
complicating the language model for 100% of the users does not seem to
be worth it to me.
Especially if in those 0.01% cases I don't see a lot of real
improvement, but rather a "good feeling" that you can write "really
strict" code even though there's nothing better in that code than if it
were written without all the complexities.
A realization that needs to be made is that beginners would be using
libraries that requires to make valid restrictions, preventing those
beginners to mess up with code they shouldn't. So even if the use case is
only valid for 0.01% of code producers, it might be valid for 20%+ of
consumers.
Being a constant contributor to open source and many libraries that now
drives most of what people consider as "Modern PHP", I constantly face
problems with the language itself being unable to achieve my needs. When
I say my needs, I mean needs of heavily used libraries, with hundreds or
thousands of other projects depending on them.I would like to see what is impossible to implement in PHP without
private classes. I mean useful functionality, not circular reference "I
need private classes because I need classes to be private" of course.
What application can not be implemented without it? I have very hard
time imagining such application.
It's not about being possible or impossible. It's about to prevent
unexpected breakages and headaches that sometimes you're not even aware.
It's about defensive programming.
I never knew TopologicalSorter was used by several users until I actually
broke its API. Imagine the frustration of all those developers with tight
deadlines having to deal with a sudden breakage, because the language it
too open to notify them about something shouldn't be used.
Every time someone questions me about the validity of private classes, I
like to exercise replacing the term "private" with "final". Final is part
of PHP language and it also there's non application that is prevented
without it.
Doctrine's code, taking advantage of "final" modifier also allowed Zend
Framework to take the right path in their implementation. We carefully
think about everything we implement there, always making sure defensive
programming is used for every class. Now if it was not for the existence of
"final" modifier, https://mwop.net/blog/2012-06-28-oop-visibility.html
history would be completely different.
The same would happen for "private" classes. It prevents consumers of
taking the wrong approach when developing their apps, other libraries, etc.
working on a bug fix. If this class was never intended to be used
outside of the scope of this project, how can restrict its usage by
outside packages? Feel free to provide me answers or alternatives, and
I'll happily agree with you that private classes is a bad idea.You can document it and talk to your users. PHP is an open-source
software, with open code and open development model. If the code is out
there, people can use it. If they use it contrary to maintainer's
recommendations, they take a risk. But that's it.Obviously, if the users used that class, it provided some functionality
they needed. If you removed that functionality, now the users don't have
it. It's not a problem that can be solved with access modifiers. In
fact, if there were access modifiers preventing users from using
necessary functionality, they'd probably fork the code and removed them.Also, working around namespace-private classes is very easy - just put
your own code in the same namespace. Works for Java too IIRC.
That's not about preventing for no reason and it should be bypassed. It's
about somehow telling users that something shouldn't be used at all.
The amount of people that takes the wrong approach is insane, and any kind
of prevention to do it is beneficial.
I managed big teams of development where I work, and controlling code
produced by 40+ developers is not easy.
Countless classes are marked as final and I bet hundreds would be final if
I could.
Handling a company source code is easy through code reviews, and also walk
a few steps to ask why a certain class is final or (hypothetically,
private).
I've walked through several scenarios of peers coming to my desk asking to
remove final because reasons and going out with a much simpler solution
without the need to remove it.
Namespace implementation is now the blocker of many new features that
could help solve complex problems that you don't see on a daily basis,
but library developers face daily. For example: allowing to assess
imported/used structs when declaring a class would drastically reduce
the complexity of Doctrine's annotation parser, which required
token_get_all and several logic to check for what you're including. IsThis is a whole bunch of things here. First of all, annotation parser is
a solution for a problem that shouldn't exist - namely, lack of
annotation support. Second, the number of code that needs to pre-process
other PHP code in PHP is minuscule compared to code doing other things.
Problem of lack of Annotations support... I've tried several times, and
made sure I was accessible enough to answer every situation or scenario
Doctrine faced and reasons for every decision taken on the library. Still,
people seem to ignore expertise and decide to take their own methods which
only solves (I'll use your numbers now) 0.01% of use cases.
Third, I'm not even sure what "allowing to assess imported/used structs
when declaring a class" means - why Doctrine annotation parser declares
a class? What are "structs"? What use has to do with class declaration?
What does Doctrine parser needs to assess, actually? I'm confused.
When I mentioned allowing to assess imported classes, I mean this:
namespace Foo;
use Bar\Woo;
/**
- @Woo\Fuzz()
*/
class Lala {}
Annotation parser needs to understand what was "use"d, so it can properly
refer to FQCN. That way, we need to somehow discover something that is only
available at source (and never at post-compile time) to know that you used
"Bar\Woo" when I'm processing "Lala" class. This is what TokenParser class
does:
https://github.com/doctrine/annotations/blob/master/lib/Doctrine/Common/Annotations/TokenParser.php
By the lack of language support, library developers need to implement
creative ways of solving problems that shouldn't exist.
Fourth, I very much question the assertion that this is the use case
library developers face daily. I've developed many libraries in my
career, and I never had to face this particular issue (unless, of
course, in my confusion - see above - I misunderstood what the issue is).there a better solution for that too? Well, I'd argue Annotations has a
market, and people are just blind if they don't wanna see it. Answering
that implementing Annotations in userland is a bad idea is like telling
a corporation shouldn't create a product because you don't like it.Not because I don't like it - but because this is not the right way
to do it. If you create a car with square wheels, I'd say it won't drive
well not because I hate squares but because I know round wheels drive
better. It's not a question of my personal aesthetic preferences.I do have a lengthy, detailed and complex plan of how namespace
refactoring could be made, what it would enable feature-wise, and how itBefore getting to the question of how, we need to pass the question of
why. And that I think is much more important.knowledge. What I don't want to see is blind resistance to language
evolution and blaming over something as badly designed without
understanding the circumstances that drove to that implementation
decision.Why do you assume this resistance is "blind"? Please do not fall into
temptation of imagining your opponents as incapable of reason. This
would not get us very far. No, there are very good reasons for
opposing complicating the language, copying Java features that make very
little sense in PHP context and overhauling the engine and introducing
massive complications to serve a tiny percentage of use cases, even
those probably produced by lack of a different feature (also lacking
because of rejection of simple solutions in a quest of building
complicated one that serves 0.01% of most complex cases).--
Stas Malyshev
smalyshev@gmail.com
--
Guilherme Blanco
Lead Architect at E-Block
Hi!
A realization that needs to be made is that beginners would be using
libraries that requires to make valid restrictions, preventing those
beginners to mess up with code they shouldn't. So even if the use case
is only valid for 0.01% of code producers, it might be valid for 20%+ of
consumers.
I don't think PHP needs more bondage-and-discipline. If beginners "mess
up with code they shouldn't" that is usually, in my experience, a sign
of library authors not serving their use case well, and not implementing
functionality that they need. Either because they didn't know, or
because their opinion about user's needs differs from user's opinion
about their needs. In this case, I'm on the user's side. Hacker's ethos
is doing things other people think can't be done.
It's about defensive programming.
I never knew TopologicalSorter was used by several users until I
actually broke its API. Imagine the frustration of all those developers
with tight deadlines having to deal with a sudden breakage, because the
language it too open to notify them about something shouldn't be used.
I'd rather say "imagine the frustration of all those developers that
rely on the library authors who are oblivious to their true needs and
still the developers at the mercy of them every day". I agree that can
be frustrating. The solution is not more detachment from the needs of
the users and more fencing out so these pesky users would have
absolutely no way to use my code in ways that I didn't foresee, because
I'm capable of foreseeing 100% of user's needs and if I'm not, it's
their fault. At least, I don't think it is.
Every time someone questions me about the validity of private classes, I
like to exercise replacing the term "private" with "final". Final is
part of PHP language and it also there's non application that is
prevented without it.
I think most of the usages of final are wrong too, and for the same
reasons. It's a claim you have foreseen all user's needs and they'd
never need anything else. It's usually wrong.
modifier, https://mwop.net/blog/2012-06-28-oop-visibility.html history
would be completely different.
There Matthew says pretty much what I say, so I agree:
I feel that frameworks and libraries should use private and final sparingly.
That's not about preventing for no reason and it should be bypassed.
It's about somehow telling users that something shouldn't be used at all.
The amount of people that takes the wrong approach is insane, and any
kind of prevention to do it is beneficial.
If the amount of people using your library in a wrong way is insane,
maybe something needs to be changed in the library? ;) If you build a
nice slick walkway and people keep walking over the grass 100 meters
aside, it may just be you've built the walkway in a wrong place.
I managed big teams of development where I work, and controlling code
produced by 40+ developers is not easy.
Countless classes are marked as final and I bet hundreds would be final
if I could.
Handling a company source code is easy through code reviews, and also
walk a few steps to ask why a certain class is final or (hypothetically,
private).
Maybe it makes sense to a company which works on its own product, never
to be used by others, and has every use of the code tightly controlled,
foreseen and signed in triplicate with TPS reports on top. My opinion
though it is more of a burden than a benefit to an open-source
environment built on extending and modifying existing code.
I've walked through several scenarios of peers coming to my desk asking
to remove final because reasons and going out with a much simpler
solution without the need to remove it.
That implies you are always smarter than any user of your classes and
have foreseen their need in advance before they even were aware of them.
It is nice to be a genius, unfortunately, there's only one of you, and
maybe another dozen of similar geniuses in existence (I'm certainly not
one of them, not even close). Others need to deal with situations where
their code is used in situations where they did not foresee 100% of
users' needs and some modification/extension should be made. Betting on
being a prophetical genius is usually a losing bet. Unless, of course,
you are. But I would rather prefer the flexibility of being able to
correct my mistakes and shortsightedness than betting on that. Which
means flexibility beats bondage-and-discipline.
Problem of lack of Annotations support... I've tried several times, and
made sure I was accessible enough to answer every situation or scenario
Doctrine faced and reasons for every decision taken on the library.
Still, people seem to ignore expertise and decide to take their own
methods which only solves (I'll use your numbers now) 0.01% of use cases.
Unfortunately I am of opposite opinion - that the Doctrine needs -
important as they are - are 0.01% of all PHP user's needs. And the fact
that we don't have solution we could build on (again, you see the
leitmotif I'm harping on - being flexible instead of trying to be ideal
at the start and freezing there) because that solution won't serve all
the needs appears very unfortunate to me.
When I mentioned allowing to assess imported classes, I mean this:
namespace Foo;
use Bar\Woo;
/**
- @Woo\Fuzz()
*/
class Lala {}Annotation parser needs to understand what was "use"d, so it can
properly refer to FQCN. That way, we need to somehow discover something
I see what you mean. This seems to be a problem because you are trying
to do compiler things without the support of the compiler. I'd say the
right solution is to let the compiler do these things...
Stas Malyshev
smalyshev@gmail.com
Hey Stas
Hi!
A realization that needs to be made is that beginners would be using
libraries that requires to make valid restrictions, preventing those
beginners to mess up with code they shouldn't. So even if the use case
is only valid for 0.01% of code producers, it might be valid for 20%+ of
consumers.I don't think PHP needs more bondage-and-discipline. If beginners "mess
up with code they shouldn't" that is usually, in my experience, a sign
of library authors not serving their use case well, and not implementing
functionality that they need. Either because they didn't know, or
because their opinion about user's needs differs from user's opinion
about their needs. In this case, I'm on the user's side. Hacker's ethos
is doing things other people think can't be done.
You don't think so, but this happened many times also with non-beginners
(what Guilherme was talking about).
Conventions are not sufficient to keep a codebase consistent and safe:
explicit rules do that.
Hi Stas,
On Sun, Aug 14, 2016 at 6:35 PM, Stanislav Malyshev smalyshev@gmail.com
wrote:
Hi!
A realization that needs to be made is that beginners would be using
libraries that requires to make valid restrictions, preventing those
beginners to mess up with code they shouldn't. So even if the use case
is only valid for 0.01% of code producers, it might be valid for 20%+ of
consumers.I don't think PHP needs more bondage-and-discipline. If beginners "mess
up with code they shouldn't" that is usually, in my experience, a sign
of library authors not serving their use case well, and not implementing
functionality that they need. Either because they didn't know, or
because their opinion about user's needs differs from user's opinion
about their needs. In this case, I'm on the user's side. Hacker's ethos
is doing things other people think can't be done.
It's a valid point of view, indeed.
But thinking from a different perspective, if you are using a tool that
does X, users shouldn't rely on Y to complete their job. Specially if Y is
already on a namespace marked as "Internal" with @private annotation and a
huge capital disclaimer that it was a private class.
It doesn't matter how much documentation you add, without language tooling
that prevents to use X as Y, all you can expect are weird use cases and
breakages, that re either not intended to work or are just not supposed to
be done at all. Like I always say "never underestimate someone's ignorance".
It's about defensive programming.
I never knew TopologicalSorter was used by several users until I
actually broke its API. Imagine the frustration of all those developers
with tight deadlines having to deal with a sudden breakage, because the
language it too open to notify them about something shouldn't be used.I'd rather say "imagine the frustration of all those developers that
rely on the library authors who are oblivious to their true needs and
still the developers at the mercy of them every day". I agree that can
be frustrating. The solution is not more detachment from the needs of
the users and more fencing out so these pesky users would have
absolutely no way to use my code in ways that I didn't foresee, because
I'm capable of foreseeing 100% of user's needs and if I'm not, it's
their fault. At least, I don't think it is.
Taking from this perspective (and also inline with your initial comment),
what you are suggesting is basically break down common, reusable piece of
functionality on its own package. Yes, that is valid, and I have nothing
against it.
The lack of breaking X into smaller packages (such as decoupling Y) doesn't
mean users should use Y at will and suddenly expect/blame about API breaks.
Every time someone questions me about the validity of private classes, I
like to exercise replacing the term "private" with "final". Final is
part of PHP language and it also there's non application that is
prevented without it.I think most of the usages of final are wrong too, and for the same
reasons. It's a claim you have foreseen all user's needs and they'd
never need anything else. It's usually wrong.modifier, https://mwop.net/blog/2012-06-28-oop-visibility.html history
would be completely different.There Matthew says pretty much what I say, so I agree:
I feel that frameworks and libraries should use private and final
sparingly.
Here you basically agreed with everything I said. I'm not saying every
single class should be private (or final), but you just agreed with Matthew
and I that there are valid use cases for each one.
That's not about preventing for no reason and it should be bypassed.
It's about somehow telling users that something shouldn't be used at all.
The amount of people that takes the wrong approach is insane, and any
kind of prevention to do it is beneficial.If the amount of people using your library in a wrong way is insane,
maybe something needs to be changed in the library? ;) If you build a
nice slick walkway and people keep walking over the grass 100 meters
aside, it may just be you've built the walkway in a wrong place.
It's always a balance between what you can do in such a small spare time
and benefits you get out of these contributions that you could use to get
your job done.
I could perfectly decouple 10 sub-packages out of ORM, but that only brings
more maintenance burden that will consume more time that I already don't
have.
I managed big teams of development where I work, and controlling code
produced by 40+ developers is not easy.
Countless classes are marked as final and I bet hundreds would be final
if I could.
Handling a company source code is easy through code reviews, and also
walk a few steps to ask why a certain class is final or (hypothetically,
private).Maybe it makes sense to a company which works on its own product, never
to be used by others, and has every use of the code tightly controlled,
foreseen and signed in triplicate with TPS reports on top. My opinion
though it is more of a burden than a benefit to an open-source
environment built on extending and modifying existing code.
Not really. I worked as Chief Architect and controlling integration
conflicts consumed most of my time while designing and planning the
platform.
Every feature had critical technical architecture points highlighted and
another series of decisions to be made by developers. When handling large
volumes of traffic, some technology decisions are required in order to keep
business online, and some critical pieces of code were heavily controlled
by key developers.
I've walked through several scenarios of peers coming to my desk asking
to remove final because reasons and going out with a much simpler
solution without the need to remove it.That implies you are always smarter than any user of your classes and
have foreseen their need in advance before they even were aware of them.
It is nice to be a genius, unfortunately, there's only one of you, and
maybe another dozen of similar geniuses in existence (I'm certainly not
one of them, not even close). Others need to deal with situations where
their code is used in situations where they did not foresee 100% of
users' needs and some modification/extension should be made. Betting on
being a prophetical genius is usually a losing bet. Unless, of course,
you are. But I would rather prefer the flexibility of being able to
correct my mistakes and shortsightedness than betting on that. Which
means flexibility beats bondage-and-discipline.
By far I'm not the most genius person I've worked with. I tend to relate
this to scope knowledge and the amount of time invested during strategy,
architecting and planning a solution. If I spend 100h exercising edge cases
out of a solution, I may have covered a substantial amount of potential
problems that may come up by developers who are purely focused on
delivering a small feature.
That doesn't mean I have covered 100% of all edge cases, but surely I've
exercised things out of a solution more than others. If I haven't, I
wouldn't be doing my architect's job.
Anyway, this is already derailing into a completely separate topic. Let's
stick to the main subject, please.
Problem of lack of Annotations support... I've tried several times, and
made sure I was accessible enough to answer every situation or scenario
Doctrine faced and reasons for every decision taken on the library.
Still, people seem to ignore expertise and decide to take their own
methods which only solves (I'll use your numbers now) 0.01% of use cases.Unfortunately I am of opposite opinion - that the Doctrine needs -
important as they are - are 0.01% of all PHP user's needs. And the fact
that we don't have solution we could build on (again, you see the
leitmotif I'm harping on - being flexible instead of trying to be ideal
at the start and freezing there) because that solution won't serve all
the needs appears very unfortunate to me.
Doctrine needs seems like a 0.01% of use cases, but they are also shared by
Symfony, Zend Framework and many other projects too.
We even downscaled a lot the suggested solution too, but it was ignored
somehow. For example, typed properties would reduce significantly our
implemented solution, because we'd rely on language support to the work for
us.
The only problem I find is that PHP is full of half-baked solutions. People
focus too much on a specific feature and do not consider a holistic
approach. Small example: people found it seriously cool to adopt ?Foo as a
nullable type. However, it complicated a lot the implementation of typed
properties (Joe had to work on a triple variable state to make it work).
Now implementation of typed properties was to blame, but I seriously think
it was the introduction of nullable types the culript.
When I mentioned allowing to assess imported classes, I mean this:
namespace Foo;
use Bar\Woo;
/**
- @Woo\Fuzz()
*/
class Lala {}Annotation parser needs to understand what was "use"d, so it can
properly refer to FQCN. That way, we need to somehow discover somethingI see what you mean. This seems to be a problem because you are trying
to do compiler things without the support of the compiler. I'd say the
right solution is to let the compiler do these things...
Yes, yes and yes. When can we get that?
I'll again reinforce that library developers have to come up with creative
ways to handle lack of support in the language. That was just one example.
Instead, the language should offer proper support and let library
developers to come up with creative tools (and now creative ways) to
improve and ease end user's development.
--
Stas Malyshev
smalyshev@gmail.com
--
Guilherme Blanco
Lead Architect at E-Block
Annotation parser needs to understand what was "use"d, so it can
properly refer to FQCN. That way, we need to somehow discover something
I see what you mean. This seems to be a problem because you are trying
to do compiler things without the support of the compiler. I'd say the
right solution is to let the compiler do these things...Yes, yes and yes. When can we get that?
I'll again reinforce that library developers have to come up with creative
ways to handle lack of support in the language. That was just one example.
Instead, the language should offer proper support and let library
developers to come up with creative tools (and now creative ways) to
improve and ease end user's development.
In the past it used to be easier to write extensions and the way I still
see it is that anything that you don't want the 'customers' to play with
should be packaged that way. It's the 'creative ways' of using the USER
interface which is the problem and as soon as you come up with some
bondage SOMEONE will work out how to bypass it!
--
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