0

This is exactly my code, I'm just stripping out everything that isn't needed:

First of all, I met this issue a lot of times and something tells me that it's something to do with PHP itself and "data structure hinting"?.

I have 3 objects, all of them respect the following interface:

interface OutputExportData { function output(); }

What this tells any object that implements it is that the object needs to output some kind of data. Let's assume that somewhere up the chain of execution, there's a foreach that goes through these 3 objects and since they all 100% have the output function, it gets called so I can get the data, my actual real usage is that I run an import of different steps that save / wrangle a lot of data and this output function will give me a list of IDs / summary of what was imported / changed based on that object's implementation.

And there-in lies the issue. Every object is free to output whatever they desire, or rather however they desire. Why is this a problem?

foreach( $objects as $object ) {
    $export_data = $object->output();
    saveToImportantList( $export_data['main_flags'] );
}

For every object, I look for a key main_flags, since I'm the original developer I'll know that for all OutputExportData objects, I need to implement this kind of array, but developers that will come and create their new objects to insert into my main flows will not know it, that's one big issue: the PHP script will break, since it can't find that key in the array.

Of course I can put it in the comments and documentation, but isn't there a way to type-hint what I expect PHP to ingest / output in the interface, such as:

interface OutputExportData {
    public function output() : array<'main_flags' => array, 'name' => string> {}
}

So that I can force, through code that whoever's implementing my interface, to respect the same return schema, so issues like the one stated above won't happen?

Concerns:

  1. I understand that in my foreach I can just simply check for these keys and that the interface provides a contract for the top-level flow of the object (through naming of the interface's functions from where we can somewhat accurately tell what they are doing), but can't an interface also be concerned with how its implementing objects' functions output / move things?
  2. This feels wrong to do: this main_flags key, I only need to use in this specific foreach and nowhere else, well, naturally as my system grows, I might use it in some other places, but for these other places, will it be relevant that my functions need to respect that structure? I'm not so sure.
  3. What if my output function encounters a fatal error and cannot, under any circumstance, output my desired schema, can't I then do:

interface OutputExportData {
    public function output() : boolean / array<'main_flags' => array, 'name' => string> {}
}

And in the end, the better question, on top of my original one is: should I even do this?

I read on interfaces a lot, for other languages too and here's a quote:

The functionality of a class does not depend on the interfaces it implements, the interface just provides a generic way of accessing the functionality.

Which is on-point, but if it was possible to do what I want, then the quote changes to:

The functionality of a class does not depend on the interfaces it implements, the interface provides a way of accessing the functionality and dictates what that functionality's end goal is.

Which doesn't sound like an interface.

4
  • PHP has classes, doesn't it? Just define your output() function to return the type Output, which is a class that has main_flags and name as members. Commented Nov 17, 2018 at 10:05
  • @MichaelBorgwardt That seems interesting, can you please share a snippet of code of how that would look?
    – coolpasta
    Commented Nov 17, 2018 at 11:46
  • @DocBrown: it says specifically that it's his code, not an example for a general situation. Commented Nov 17, 2018 at 16:02
  • @coolpasta: it's been too long since I've done PHP, fortunately someone else came to the rescue. Commented Nov 17, 2018 at 16:03

2 Answers 2

5

As Michael Borgwardt suggested in comments, the PHP ways to describe a 'data structure' are classes and interfaces. I'll show an example with an additional interface, but you could use a class instead.

Write an interface to describe what should be returned as output:

interface Output
{
    public function getMainFlags() : array;
    public function getName() : string;
}

Then edit your OutputExportData interface to declare that it returns Output:

interface OutputExportData
{
    public function output() : Output; 
}

Now any class that implements OutputExportData will be constrained to return a value that is an instance of Output, and the calling code can be assured that the methods getMainFlags and getName will exist and return string and array.

3
  • @coolpasta: welcome to the world of statically typed code. Commented Nov 17, 2018 at 16:01
  • @MichaelBorgwardt Awesome, but I assume there's no way to actually also define a structure I'd want to be respected, as in my question, the array must have "this key".
    – coolpasta
    Commented Nov 17, 2018 at 19:21
  • 1
    My suggestion is to use an object with a class instead of an array. Generally it does the same thing, and also actually runs faster than array since the system doesn't have to search for a key by name. So instead of saveToImportantList( $export_data['main_flags'] ); you will write saveToImportantList( $export_data->getMainFlags());
    – bdsl
    Commented Nov 17, 2018 at 19:37
1

What you descibed is called design by contract. The requirement that the returned array needs to contain a key 'main_flags' is called a postcondition.

The language element interface (in PHP, or other languages like Java or C#), however, does not provide such semantics constraints, interfaces can only be used to define the syntactical part of a contract, not the semantical part.

For many languages, even if they don't provide DBC "out of the box", there are frameworks which can help to implement preconditions, postconditions and invariants directly in code. The Wikipedia link above mentions some frameworks for PHP (disclaimer: I did not try them by myself, you have to evaluate by yourself).

Without such a framework, the typical way of ensuring a postcondition is

  • write precisely into the documentation of the interface of what the function needs to return

  • write unit tests which check the postcondition for each interface implementation

  • add validation code to the calls of the output function which check for the presence of the key, and make the script break immediately when the postcondition is not fulfilled, or make the script handle it gracefully (but without masking the error).

If these measures are enough, or if you need a more strict way of checking postconditions and if using a DBC framework is worth it, is something you need to find out by yourself, for your environment and situation.

6
  • I see, but still, can you share your thoughts on actually doing this? What are the downsides? I'm thinking of writing a thing close to this and have an idea on how to do it.
    – coolpasta
    Commented Nov 17, 2018 at 9:18
  • @coolpasta: you want me to write something about the downsites on writing clear documentation, unit tests and validation code? Seriously?
    – Doc Brown
    Commented Nov 17, 2018 at 9:21
  • I want you to share your thoughts on whether writing semantic constraints in interfaces is a good thing or not.
    – coolpasta
    Commented Nov 17, 2018 at 9:28
  • @coolpasta: "writing semantic constraints in interfaces" by the means I scetched (documentation, unit tests and validation code), or by other means? Sure it is a good idea to use clear documentation, unit tests and validation code for any piece of code which has to be maintained over a longer period by different people.
    – Doc Brown
    Commented Nov 17, 2018 at 9:39
  • By other, language-provided means, as I tried to exemplify with some possible PHP code. I wasn't asking / refering to the documentation / tests at all and I think it's not a point of discussion whether they're a good idea or not. Strictly looking for the upsides / downsides of the existence of a mechanism that allows me to dictate, in an interface, what a function should output and how.
    – coolpasta
    Commented Nov 17, 2018 at 9:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.