Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[RFC] CDS-io #32

@TorbenKoehn

Description

@TorbenKoehn

CDS-io

This document describes common interfaces for basic input and output operations of a PHP application.

In order to fully understand the scope of this CDS, read CDS-io-stream and CDS-io-console after this one as well.

Why

Most entities and parts of an application provide some kind of input and output mechanism. Some examples of mechanisms with input and output:

class Logger
{
    private $output;

    public function getOutput();
    public function write($message);
}


class Importer
{

    /**
     * Input would be an object containing e.g. command-line options, target path etc.
     * Output would be an object providing methods to write a log to (e.g. log-output, console-output, HTML-output)
     */
    public function import($input, $output);
}


class ConsoleCommand
{

    public function run($input, $output);
}


class App
{

    public function dispatch($input, $output);
}

In order to let these work with each other (e.g. the same output passed to a console-command or logger can be hooked to an application afterwards), we define two simple and basic interfaces providing the most basic and required methods to transfer data between them.

The Cds\Io namespace

The Io namespace is a parent-namespace and may contain further sub-namespaces in the future (Namely Console and Stream).

We use Cds instead of Psr to avoid future clashings with the Psr-classes (They may or may not add an Io namespace later on)

The InputInterface

<?php

namespace Cds\Io;

/**
 * An interface declaring that the implementor provides input.
 * 
 * It should be implemented by mechanisms that provide any kind of parsable scalar data
 * and can be type-hinted to read from dynamically.
 */
interface InputInterface
{

    /**
     * Receives a message from a backing message storage of any kind.
     *
     * The backend can be a stream, HTTP Input, a URI or query-string.
     *
     * Reading should always increase an internal pointer so that the next call to read
     * will return the next message or the next part of it.
     *
     * If a length is passed, only the part up until this length of the message is read
     * and the next call to read should return the part following after it.
     * If the passed length is longer than the left messages-content, it will read up until
     * the messages-content are empty.
     *
     * @param int|null $length the length of the message part to read.
     *
     * @return string the part of the message that has been read or an empty string, if no messages are available anymore
     */
    public function read($length = null);
}

The read-method is pretty self-explanatory. It's supposed to return a string from the backed message-storage of any kind and advance.

If no length is passed to read, the implementor can choose a default length manually. This is important as not all message-storages require a length at all (e.g. array of messages)

The OutputInterface

<?php

namespace Cds\Io;

/**
 * An interface declaring that the implementor accepts output.
 *
 * It should be implemented by all kinds of output-acceping mechanisms
 * and can be referenced in type-hints to get entities you can output to
 **/
interface OutputInterface
{
    /**
     * Sends a message to the backing message storage of any kind.
     * 
     * The backend can be a stream, HTTP output, an array of strings or anything else
     * as long as it is able to receive simple and generic strings and work with them
     * 
     * The write-method SHOULD always advance an internal pointer (e.g. array-index or stream position)
     *
     * @param string $message the message to send to the backing output storage 
     * 
     * @return int the bytes that have been written to the objects contents
     */
    public function write($message);
}

This is as short and simple as it can be. An implementor that declares that it accepts output only needs to implement one write method.

An example of an implementor would be a Logger or a HTTP Request body/stream

Example implementations

A basic logger utilizing the OutputInterface.

Passing another class implementing OutputInterface will redirect all messages to that one.

<?php

use Cds\Io\InputInterface;
use Cds\Io\OutputInterface;

interface LoggerInterface implements OutputInterface
{

    public function log($message, $type = null);
    public function info($message);
    public function error($message);
}


class Logger implements LoggerInterface
{

    const TYPE_INFO = 'INFO';
    const TYPE_ERROR = 'EROR';

    private $output;

    public function __construct(OutputInterface $output = null)
    {

        //Assuming "Stream" implements OutputInterface
        $this->output = $output ?: new Stream(fopen('/some-default-logfile.log'));
    }

    public function write($message)
    {

        return $this->output->write($message);
    }

    public function log($message, $type = null)
    {

        $type = $type ?: self::TYPE_INFO;
        $timeStamp = date('d.m.Y');

        return $this->write("[$timeStamp]\t[$type]\t$message");
    }

    public function info($message)
    {

        return $this->log($message, self::TYPE_INFO);
    }

    public function error($message)
    {

        return $this->log($message, self::TYPE_ERROR);
    }
}

Advantages

Output can easily be replaced with another one.

Disable logging:

<?php

use Cds\Io\OutputInterface;

class NullOutput implements OutputInterface
{
    public function write($message) {

        return 0;
    }
}

$logger = new Logger(new NullOutput());

Echo-Logging

<?php

use Cds\Io\OutputInterface;

class EchoOutput implements OutputInterface
{
    public function write($message) {

        echo $message;

        return strlen($message);
    }
}

$logger = new Logger(new EchoOutput());

Saving output for later use

<?php

use Cds\Io\OutputInterface;

class ArrayOutput implements OutputInterface
{

    private $messages = [];

    public function getMessages()
    {

        return $this->messages;
    }

    public function write($message) {

        $this->messages[] = $message;

        return strlen($message);
    }
}

$logger = new Logger(new ArrayOutput());

Fancy custom-logging

<?php

use Cds\Io\OutputInterface;

class SqlLogOutput implements OutputInterface
{
    /* ... */

    public function write($message) {

        $logEntry = new LogEntry($message);
        $this->entityManager->persist($logEntry);

        return strlen($message);
    }

    public function flush()
    {

        $this->entityManager->flush();
    }
}

$logger = new Logger(new SqlLogOutput());

Even more important is the fact that the Logger itself is a valid output as well, so if you don't want to be specific but rather say "Write everything here to whatever log-file that Logger is writing to currently", you can simply create something based on that logger

$advancedLogger = new SomeAwesomeAdvancedLogger($otherLogger);

An app that can call controllers on different kinds of input and provide different output-formats

class App
{

    public function run(InputInterface $input, OutputInterface $output)
    {


        $args = ['controller' => 'index', 'action' => 'index', 'id' => null, 'format' => 'html'];

        if ($input instanceof HttpInput)
            $args = array_replace($args, $input->getQueryParams());

        if ($input instanceof CliInput)
            $args = array_replace($args, $input->getOptions());


        $dispatchOutput = new DispatchOutput($output);
        $this->dispatcher->dispatch($args, new DispatchOutput($output));

        switch ($format) {
            case 'html':
            case 'default':

                return $output->write($this->render($dispatchOutput->getViewName(), $dispatchOutput->getParameters()));
            case 'json':

                if ($output instanceof HttpOutput)
                    $output->setContentType('application/json', 'utf-8');

                return $output->write(json_encode($dispatchOutput->getParameters()));
            case 'xml':

                if ($output instanceof HttpOutput)
                    $output->setContentType('text/xml', 'utf-8');

                return $output->write(xml_encode($dispatchOutput->getParameters()));
        }
    }
}

It doesn't get more dynamic than that. The only thing your controllers need to do is receiving, handling and returning data (Based in InputInterface and OutputInterface in the best case)

Important Notes

These two interfaces are not meant to be a representation of STDIN (php://input) or STDOUT (php://output). Those would rather be represented by a Readable and Writable stream that implement InputInterface and OutputInterface respectively.

It's just important that both can be passed as valid Input/Output instances.

This CDS does not force any implementation detail on the implementor. read and write are common, english named, non-abbrevated methods found in almost all programming languages out there.

Notice that these interfaces are completely compatible to PSR-7's StreamInterface. Read CDS-io-stream regarding this.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions