Recently, I discovered that there is a DateTimeInterface. As I was working with \DateTime and \DateTimeImmutable, I thought that it'd be good to typehint for.
Yet on the Changelog on php.net it says that since 5.5.8:
Trying to implement DateTimeInterface raises a fatal error now. Formerly implementing the interface didn't raise an error, but the
behavior was erroneous.
This reads that I never be able to implement my own CustomDateTime class implementing the DateTimeInterface, which seems rather odd.
Should I use the DateTimeInterface in my userland code?
My gut feeling tells me not to. Yet it also doesn't feel right to typehint the object, i.e.
#var \DateTime|\DateTimeImmutable|\MyCustomDateTime
You can use the DateTimeInterface typehint when you expect either one of the native DateTime variants, e.g. \DateTime or \DateTimeImmutable.
But as the docs say
It is not possible to implement this interface with userland classes.
So if you plan to implement \DateTimeInterface with your own implementation, this will not work, e.g.
class MyDateTime implements \DateTimeInterface {} // will raise an error
However, you can create your own interface and adapt the native DateTime classes.
interface MyDateTime { /* some methods */ }
class NativeDateTime implements MyDateTime
{
private $dateTime;
public function __construct($args = 'now') {
$this->dateTime = new \DateTimeImmutable($args);
}
/* some methods */
}
Or you can extend the native DateTime variants, which will then implement the DateTimeInterface obviously, e.g.
class MyDateTime extends \DateTimeImmutable
{
/* your modifications */
}
var_dump(new MyDateTime instanceof \DateTimeInterface); // true
Details why you cannot implement your own classes implementing \DateTimeInterface:
https://github.com/php/php-src/pull/512
https://www.mail-archive.com/internals#lists.php.net/msg79534.html
tl;dr core functions that accept DateTime and DateTimeImmutable (DateTimeInterface basically) operate directly on timelib structures hence they will break if you pass them a user-defined class. There's a workaround though - it's to always call user-defined class's method directly from built-in functions, so if you would call (new DateTime())->diff(new UserDefinedDateTime()) it would call UserDefinedDateTime#diff in the end which would need to implement the logic.
I would say you should not.
The \DateTimeInterface is an implementation detail of ext/date that should not have been exposed to userland code.
See the RFC created for removing it: https://wiki.php.net/rfc/drop-datetimeinterface
You are encouraged to type hint against DateTimeInterface when you accept
DateTime
DateTimeImmutable
Any class derived from these
You can however not implement DateTimeInterface without extending from either DateTime or DateTimeImmutable due to internal details.
You can ignore https://wiki.php.net/rfc/drop-datetimeinterface as by now - years after the question - we know that the community decided against removing the interface.
Related
This is just an example to illustrate my issue. Quite often, I am looking for (vendor) code that executes a method, but end up in the interface (which I guess is implemented by the code I am looking for).
In a Symfony app, I have a function Example()that uses $logger, which is typehinted with LoggerInterface. I use the correct interface by adding to my file: use Psr\Log\LoggerInterface;. In my function I use $logger to log an error with the method error() like so:
public function Example(LoggerInterface $logger)
{
$logger->error('There was some error.')
}
When I open the file that contains the LoggerInterface I can see how this method:
/**
* Runtime errors that do not require immediate action but should typically
* be logged and monitored.
*
* #param string $message
* #param array $context
*
* #return void
*/
public function error($message, array $context = array());
This can be useful for the documentation and for seeing which arguments one must pass.
My question: how do I find the method that does the actual work (i.e. that is being executed) and how does the $logger 'finds' this through the interface?
In a more generic PHP example, when I need a dateTime object I will use new DateTime(); (with a use statement: use DateTime;). Then I could call the method format() on it. When I want to find out exactly what format() does, looking for it in the class DateTime, all I find is:
/**
* Returns date formatted according to given format.
* #param string $format
* #return string
* #link https://php.net/manual/en/datetime.format.php
*/
public function format ($format) {}
Again I wonder: how to find the method doing the work?
Psr\Log\LoggerInterface is, as its name suggests, an interface. This means that it specifies the methods an inheriting class must implement, but – as you've seen – it doesn't provide any code for those methods.
Any class written to implement Psr\Log\LoggerInterface must include all the methods listed in the interface. Any such class will satisfy the type declaration in the method signature.
The actual class of your object can be determined by running get_class() on the object in question, e.g.
public function Example(LoggerInterface $logger)
{
$logger->error("I am an instance of class " . get_class($logger));
}
With respect to native PHP classes, although the documentation may be written to look like they are standard PHP classes with methods and properties, this is not the case. They are written in C, which you can find in the repository.
Taking your example of DateTime::format(), it's first defined as a method of the DateTimeInterface. This is implemented by the DateTime class, where it is simply specified as an alias to the date_format method. Unless you're very good with C, I'd suggest sticking to the documentation
Multi-tier question, deserves a multi-tier answer.
1. Configuration
Specifically for logger, it can be any logger that follows Psr\Log standards, but symfony is configured to use monolog.
You can see detail on: https://symfony.com/doc/current/logging.html (disclaimer: I'm not symfony expert but this looks logical).
So - logger is whatever you configure.
2. Programming style
When you are programming - anywhere inside a function and want to know exact instance of logger (or any other object), you can call get_class() function and get a specific instance class name.
Details on: https://secure.php.net/manual/en/function.get-class.php
3. Debugger
You can use Xdebug, add breakpoint to specific function and in variable view check instance class name.
4. PHP native objects
You can find exact functionality in PHP source code: https://github.com/php/php-src
I have a bunch of little procedural functions that manipulate DateTime in various ways, and it occurs to me that it might be easier to work with if I extend the DateTime class and turn these functions into methods of the class.
Okay, so easy peasy I can just do:
namespace My\Project;
class DateTime extends \DateTime {
... various new methods etc...
}
BUT... I also work with DateTimeImmutable objects, and would also want these additions in that class.
Is there some way to extend both of those classes to include my new functionality without repeating code? (Yes I know I can copy/paste the new methods into both new classes. Trying to be DRY here.)
Doesn't PHP have some way to "sideload" code into a class? Or a way to extend DateTimeInterface but also include the code specific to DateTime and DateTimeImmutable?
Is it bad practice to extend common classes like this? (Is it going to bite me in the butt down the road?)
Just create the two classes, each of those extending either DateTime or DateTimeImmutable, and a trait with all your "new methods, etc" which you can use directly in both of them.
trait Convenient {
public function echo() : void {
echo "bar\n";
}
}
class A extends \DateTime {
use Convenient;
}
class B extends \DateTimeImmutable {
use Convenient;
}
Regarding "best practice", that's a matter of opinion; but it's hard to imagine anyone objecting to extending base classes. It's done all the time for a lot of valid reasons. Ideally client scripts should be targeting the interfaces, not the classes anyway.
There are couple of well known and widely use libraries, like Carbon or Chronos that extend on the base date time objects. You may want to check those out before reinventing them.
In PHP 7.1.4, using strict typing, I have a simple object oriented setup involving some interfaces, and some classes implementing those interfaces. Below example, as you would expect, works fine.
declare(strict_types=1);
interface Loginable {
public function login();
}
interface Upgradeable {
public function upgrade(): Loginable;
}
class Person implements Upgradeable {
function upgrade(): Loginable {
return new PersonAccount();
}
}
class PersonAccount implements Loginable {
public function login() {
;
}
}
Notice how the upgrade function inside the Upgradable interface requires a Loginable return type, which is another interface. The upgrade method inside the Person class, in this example, specifies a Loginable interface as its return type to match the stipulation of the interface.
However, if I now attempt to specify the return type of the upgrade method of the Person class more accurately, I run into a fatal error.
class Person implements Upgradeable {
function upgrade(): PersonAccount {
return new PersonAccount();
}
}
Please observe that what I am trying to accomplish here is to specify that the upgrade method will return an object that implements the interface that is the required return type according to the interface implemented by the class. This seems very logical and correct to me, however, PHP will say:
Fatal Error: Declaration of Person::upgrade(): PersonAccount must be compatible with Upgradeable::upgrade(): Loginable in [...]
As it has already been pointed out by yivi at https://stackoverflow.com/a/49353076/9524284 what I am trying to accomplish is not possible.
I would accept the complaint of PHP if there were extended classes involved, because the extending class could override the original method, and that way there would be no guarantee of the correct return type. However, in the scenario described above, classes are not extended. There are only implementations of interfaces, the whole purpose of which is to explicitly guarantee the correct implementation.
Please shed some light on the reasoning behind the refusal of PHP to accept the above described way of declaring return types!
You're describing a type-reasoning feature called covariance, which is itself a consequence of the Liskov Substitution Principle.
As of PHP 7.4, this works as you expect. (See the RFC implementing the behavior for details.)
Prior to then, this was discussed on internals. As was stated in one such conversation:
if an implementation better than satisfies the requirements defined by an interface, it should be able to implement that interface.
So, yes, PHP should allow covariance as you describe. But realize: it's not that PHP refuses to implement covariance, it's that PHP has not yet implemented covariance. There are some technical hurdles to doing so, but they're not insurmountable. As was stated in that same thread on internals by a core maintainer:
It's doable, it just hasn't been done.
If you'd like to produce an RFC and a PR, please do so. Until then, it's just an unfortunate status quo of the ever-evolving PHP object system.
I was going through the Laravel's Illuminate and I noticed it has an interface for nearly every implementation.
What's the exact purpose of this? Does it have any current use, or is it more to make the framework as scaleable as possible?
In software engineering contracts are more valuable than their implementations.
Here's a few reasons:
You can test classes which depend on an interface without relying on an interface implementation (which itself may be buggy).
Example with PHPUnit :
//Will return an object of this type with all the methods returning null. You can do more things with the mock builder as well
$mockInterface = $this->getMockBuilder("MyInterface")->getMock();
$class = new ClassWhichRequiresInterface($mockInterface);
//Test class using the mock
You can write a class which uses a contract without needing an implementation e.g.
function dependentFunction(MyInterface $interface) {
$interface->contractMethod(); // Assume it's there even though it's not yet implemented.
}
Have a single contract but multiple implementations.
interface FTPUploader { /* Contract */ }
class SFTPUploader implements FTPUploader { /* Method implementation */ }
class FTPSUploader implements FTPUploader { /* Method implementation */ }
Laravel offers support of the last one using its service container as follows:
$app->bind(FTPUploader::class, SFTPUploader::class);
resolve(FTPUploader::class); //Gets an SFTPUploader object
Then there's also the fact that its easier to document interfaces since there's no actual implementations in there so they're still readable.
I love how in most cases you can use a Factory Pattern to help you adhere to SOLID development. The one thing I don't quite understand is how to get your IDE to recognize usages when you build your factories. For example:
<?php
class ProductFactory {
public static function build($product_type, $name) {
$productClass = 'Product' . ucwords($product_type);
if (class_exists($productClass)) {
return new $productClass($name);
} else {
throw new Exception("Invalid product type given.");
}
}
}
My IDE does not recognize $productClass as a usage for a given class because it can be an instance of multiple classes. I understand that. However, how can I tell my IDE where to find these usages? Do I need to list ALL of them in the docblock?
/**
* #var ProductSofa $class
* #var ProductChair $class
*/
Is that the only way?
Using docblocks in order to typehint your class properties is the best way to handle with this. I've been using zend2 for quite a while now, and other than typehinting class constructor parameters, adding a docblock to each property is the only way, because usually when you get an instance from a factory, you use some kind of ServiceLocator that by parameter, receives a name of a factory you want to instantiate, and its a string, so your IDE has no way to relate that string to an actual class.
Since there is no standard PHPDoc for this feature, you'll need an IDE-specific solution. If you're using PHPStorm or IntelliJ IDEA, they have built-in support for documenting factory method return types, which can be further enhanced by this plugin.