Category: General

  • Celebrating 5 Years of ddev

    Celebrating 5 Years of ddev

    Today marks a significant milestone for us at Valantic CEC (formerly known as netz98) as we celebrate five fruitful years of utilizing ddev. Our journey with this remarkable tool has not only streamlined our development processes but also brought about a transformation in how we approach our projects. As we reflect on the past five years, it’s a perfect opportunity to share insights into how ddev has benefited our team, enhanced our workflows, and contributed to our overall success.

    A Game Changer in Development

    When we first adopted ddev, our development landscape was cluttered and chaotic. Developers struggled to set up their environments consistently, leading to inefficiencies and frustrations. We needed a solution that would simplify our workflow and empower our team to focus on what really matters: delivering high-quality e-commerce solutions for our clients.

    ddev emerged as that solution. As a powerful local development environment, it allows developers to quickly spin up and manage complex applications with little configuration required. The ease of use and robust capabilities made it an instant favorite within our team. Our developers can now set up projects with just a few commands, eliminating the days spent wrestling with environment configurations.

    Supporting the Community

    Our commitment to ddev extends beyond our internal use; we actively support the tool and its community. We maintain the official OpenSearch addon, which enhances ddev’s capabilities and ensures that our development environment is equipped with the latest features. Additionally, we’re proud to provide an n8n addon, allowing for seamless integration of workflows and automation in our projects. You can check it out here: ddev-n8n.

    Moreover, our contributions to ddev are not just about the tools we support. Over the years, we have shared our knowledge through various articles and demonstrations, making it easier for other developers to adopt ddev in their own workflows. For instance, our demo setup for Magento Open Source, available at ddev-magento-demo, serves as a valuable resource for those looking to get started with ddev and Magento.

    Streamlining Development Processes

    One of the most significant impacts of ddev on our organization has been its ability to streamline developer setups. By establishing a standardized development environment, we have drastically reduced onboarding times for new team members. They no longer need to spend hours configuring their local machines; instead, they can dive straight into development. This uniformity not only boosts productivity but also enhances collaboration, as everyone works within the same framework.

    Furthermore, the flexibility offered by ddev facilitates the use of numerous services tailored to our clients’ needs. Whether it’s managing databases or running background jobs, our developers can configure the environment to suit the specific demands of a project with ease.

    We can easily transfer configurations for several commonly used services between projects. In the meantime, most of the developers are experienced enough to modify or extend the setup.

    A Heartfelt Thanks to Randy Fay

    As we celebrate our fifth anniversary with ddev, we want to extend our heartfelt congratulations to Randy Fay, the maintainer of this exceptional tool. Your vision and commitment have made ddev a cornerstone of our development processes. Knowing that we have a dedicated maintainer behind ddev gives us confidence in its ongoing evolution. We genuinely appreciate the hard work and dedication that goes into maintaining such a powerful tool.

    We also do not forget Stas Zhuk for all the help as Co-Maintainer of the project.

    Looking Ahead

    As we move forward, we remain excited about the possibilities that ddev presents. The landscape of e-commerce development is constantly evolving, and so are the needs of our clients. Embracing tools like ddev allows us to stay agile, respond to market changes, and ultimately deliver better solutions for our customers.
    For those who have yet to explore what ddev can offer, I encourage you to give it a try. Experience the benefits of a streamlined development process, enhanced collaboration, and a supportive community. With ddev in your toolkit, tackling complex e-commerce challenges becomes not just achievable but enjoyable.

    In conclusion, let us celebrate not only our past with ddev but also the exciting future ahead. As we continue to leverage its capabilities, we invite you to join us on this journey. Together, we can build efficient, scalable, and powerful e-commerce solutions that empower businesses to thrive in the digital marketplace. Here’s to many more years of innovation and success!

  • Request data through a proxy with GuzzleHttp – PSR-7 compliant

    Request data through a proxy with GuzzleHttp – PSR-7 compliant

    When a Magento shop – or any other PHP application – is required to request data from or transmit data to a remote server, it is often necessary to redirect the traffic through a Http-proxy server. In my case this was essential because the called endpoint allows only certain IP addresses to access the requested ressource. Since I am working from various locations, each having a different IP address, I had to find a way that certain Http-requests are routed through the company’s proxy server. This article is going to describe what a proxy actually does for us, why I cannot and should not use PHP’s environment variables and what the best solution is for scenarios where PSR-7 / PHP-Http in combination with GuzzleHttp might be.

    What about PSR-15?

    Good point! This text will discuss Http middleware components on client side with the example of adding proxy configurations to a request. The relatively new PSR-15 standard handles the server side and focuses mainly on manipulating the response which is sent back to the calling host. Nevertheless you will get a lot of useful information from the standard and the explanation.

    Why don’t you simply use the the HTTP_PROXY environment variable?

    Guzzle indeed accesses the PHP environment variable HTTP_PROXY and routes every request through the specified proxy, if this value is actually configured in php.ini or by using the setenv()-command. But for security reasons this feature is only available when the script is called from the shell (checked using method php_sapi_name()). More information on the vulnerability and the reason why GuzzleHttp declines to use the settings from environment variables can be found at httpoxy.org.

    GuzzleHttp offers a proxy-configuration within the request()-method – why not use this?

    Because it it not PSR-7 compliant! The HTTP message interface simply does not provide a method with name request that allows the developer to pass an options-array in which we could add our proxy-configuration. Instead the interface does only declare a method sendRequest that accepts an object of type \Psr\Http\Message\RequestInterface which also does not offer any proxy configuration.

    Middleware and Handlers

    Middleware components are a powerful – and PSR-7 compliant – way to manipulate Http requests before they’re sent to a remote server.

    In an example scenario where it would be required to send authentication data in each server call, it’d sure be exhausting to update the credentials for each method call all over your application’s code. Adding a middleware layer that automatically adds the necessary data to each request, before it is transmitted, will reduce the code lines to be updated to just a single one (maybe two).

    A middleware is simply a method that resides between the command for sending a request and the actual transmittion. It allows to add, remove or modify data, the target-URL or any other property of the \Psr\Http\Message\RequestInterface as well as additional configuration, that is important for data transfer.

    More information on the PSR-7 standard is given in our in-depth series.

    Create a middleware and add it to the HandlerStack

    The diagram below sketches on the top half the classical way of sending a request: The developer adds a a sendRequest command along with the connection and data settings hold by $request and passes it to a various transmittion mechanism, e.g. GuzzleHttp.

    In the bottom half of the diagram two sample middleware methods m1 and m2 are added to stack that resides between sending the request and the actual transmittion. Both methods are free to modify the request or the configuration hold by $options and are executed one after another, as described by the chain of responsibility, part of the infamous Gang of Four.

    The following implementation has proven to be stable but might be a bit more complex than what is actually necessary to create a middleware method. The reason for that is because of a better test-ability and more flexibility for future add-ons.

    Class ProxyMiddleware

    First step is to create a new class called ProxyMiddleware that is responsible to prepare the concrete middleware method for the handler stack, managed by GuzzleHttp. This class has static method, which holds the proxy settings (URL and port) and will return a closure that will be pushed onto the handler stack later.

    public static function getProxyMiddleware($serverUrl): callable
    {
        self::$proxyUrl = $serverUrl;
    ​
        return function (callable $handler) {
            return new ProxyMiddleware($handler);
        };
    }

    As soon as the closure is called during data transmittion a new instance of the ProxyMiddleware-class is created and invoked by the stack dispatcher. Therefore we need to implement the actual middleware-call inside of the magic __invoke-method:

    public function __invoke(RequestInterface $request, array $options)
    {
       $fn = $this->nextHandler;

       $options['proxy'] = [
           'http'  => self::$proxyUrl,
           'https' => self::$proxyUrl,
      ];

       return $fn($request, $options);
    }

    It’s that simple! We just add the proxy information to the $options-array and pass the new information onto the method described by $nextHandler. That was easy!

    Add our middleware to the handler stack

    After we have created the middleware handler, we need to tell GuzzleHttp to actually execute it after the sendRequest-command is executed. For this second step we need to instantiate a new HandlerStack-object using the class’ static create-method.

    $handlerStack = \GuzzleHttp\HandlerStack::create();
    $handlerStack->push(ProxyMiddleware::getProxyMiddleware('proxy_url'), 'middleware_id');

    $options['handler'] = $handlerStack;

    $httpClient = \Http\Adapter\Guzzle6\Client::createWithConfig($options);

    Now we simply push the previously described closure onto the stack together with an identifier-string. Allthough this ID is optional, it will be more than helpful for debugging your application, since Guzzle adds a couple of default middlewares to the stack and you would end up in a unmanageable mess.

    After we have created and configured the handler stack, we simply use the static createWithConfig-method that allows us to inject a options-array into the instantiation of a new Guzzle PSR-7 compliant Http client.

    From now on, all requests that are sent using this Http client will be transmitted with your middleware executed inbetween and having the proxy setting configured!

    Caveat: Unit Testing

    Testing your new middleware-class might be a little bit tricky. I just want you to know that closures, like the class member $nextHandler can be mocked using PHP’s pre-defined core class \stdClass and the magic __invoke method. In case you’re using PHPUnit, the following snippet might be helpful:

    $nextHandlerMock = $this->createPartialMock(\stdClass::class, ['__invoke']);

    More information

  • A visit at our friends of Atwix in Lviv/Ukraine

    A visit at our friends of Atwix in Lviv/Ukraine

    Last month we received an invitation by our friends of Atwix to attend their Barcamp in Lviv/Ukraine. My colleague Oleksandr and me were happy to join it. Oleksandr was the perfect mate, because he was grown up in Ukraine and so he knows everything about the traditions and local specialities.

    Our journey started in Frankfurt. After a stopover in Munich we arrived on site with enough energy to explore the beautiful city of Lviv.
    Oleksandr introduced me to the local delicacies like Borsch (Борщ), Blini (млинці) with Cherry and Varenyky (вареники).

    Traditional Ukraine Food

    After an delicious meal we met among others Slava Kravchuk (CEO of Atwix), Yaroslav, Maria Zayak, Tomislav Bilić (CEO of Inchoo) and Max Yekaterynenko (Director of Community Engineering at Adobe).

    Barcamp

    The Barcamp started with a breakfast where the attendees already had the chance to get to know each other.

    The agenda of the Barcamp was created in different way compared to previous Barcamps I attended. There were two parallel tracks with already defined talks and discussions. I had the honor to have a talk about Gitlab CI Build Pipelines.

    Agenda

    As you can see, there were a lot of interesting talks about all the hot Magento stuff like PWA. Also non Magento related topics like Remote Working were part of the agenda.

    After a long day with a lot of good content and discussions the day ended with a party at a very cool location where we tasted local food and drinks together with the Atwix and Inchoo guys.

    Magento Contribution Day

    Atwix as No. 1 Contributor had organized a Magento Contribution Day. A Contribution Day is a good chance to dive into the source code of the Magento Core.

    The procedure is very easy. Pick some topics from Magento Contribution backlog and try to fix, solve or invent stuff. If you do not know what you can do, visit the portal to start your way to contribute.

    https://opensource.magento.com/

    It is also possible to contribute to non code related topics like the developer documentation. I personally picked up an old bug ticket which was not edited since 2016.

    Conclusion

    We had a lot of good discussions with Atwix and Inchoo developers and project managers about PWA, Magento, Certification and a lot of more topics. I was able to share some insights about Gitlab-CI pipelines which shows the netz98 way of building projects.
    The evening event was great. The Contribution Day, too…

    Thank you for the hospitality. See you next time in Lviv.

  • How to avoid security issues in Composer dependencies

    Composer is a great tool for requiring third party modules and software packages for your project. It’s an essential part of the current Magento 2 project structure.

    Because of the possibility to add more and more modules it is also getting more and more difficult to keep track of relevant security updates. That is especially the case when required modules have further requirements.

    Here are 3 tips how to improve your project’s security

    1. Subscription of third party repositories (when using sticky version numbers)

    If the applied module is published on Github you can subscribe to the repository. Github then informs you via email about changes of the code. If the email contains relevant information regarding security issues, the version of the required module can be increased in your project‘s composer.json file. For updating you just have to type:

    composer update vendor/module

    Subscribing @ Github

    2. Avoid static version numbers

    Compared to option 1 it would be much more safe to manage the applied parts and the own project via semantic versioning. In those cases you can define the dependencies as follows:

    composer require vendor/module=~3.1

    In this specific case you would get all versions between 3.1.0 and 3.2.0 by executing a “composer update”. If the vendor fixes the module, he just increases the version number at the third position and the new code will be applied and implemented with the next update.

    If the vendor implements a new feature however, he increases the major or minor version, e.g. 3.2.x or 4.0.x. The code will only be applied by a manual action of a developer. Reviewing the code before committing the new version would be highly recommended.

    3. Use SensioLabs Scanner

    While option 2 is already a very cool and flexible solution, the developers of the vendors of the applied modules still need to be informed about the security issues and they need to fix them. But the moment they release a new version might be too late. As a developer, you definitely want to know about issues immediately, to disable or replace the dangerous module.

    SensioLabs – creator of the Symfony framework – created a database containing known security issues of several packages (Magento, Shopware, Zend Framework etc.) and they allow you to get these information via an api or a cli client. These tools scan a given composer.lock file. For every current commit or tag they check for an entry inside the database. After processing the file you get a list of findings if there were any.

    Security Check: Success

    Security Check: Error

    The client can be easily installed via Composer.

    composer require sensiolabs/security-checker

    Depending on your project and your required Symfony dependencies you might need to use a specific version of the tool.

    To run it, all you have to type is:

    vendor/bin/security-checker security:check

    By today there’s only one entry for Magento 2 in this database:

     

    Links

  • PSR-7 Standard  – Part 6 – Server Requests

    PSR-7 Standard – Part 6 – Server Requests

    This post is part of series:


    In Part 3 we already discovered the RequestInterface which is used on client side. In this part, we have a more detailed look on the server side.

    The Server Request inherits all methods of the RequestInterface and has 13 additional methods. Six methods are available to setup the request object:

    • withCookieParams
    • withQueryParams
    • withUploadedFiles
    • withParsedBody
    • withAttribute
    • withoutAttribute

    Like on client side we have to be careful. The Server Request is designed to be immutable (see Part 3). If we look in the code of the Guzzle\Psr7 implementation of the Server Request, we can see that all this methods are needed to create complete request from all global variables

    ($_SERVER, $_COOKIE, $_GET, $_POST, $_FILES) which we will get from PHP.

    
    /**
      * Return a ServerRequest populated with superglobals:
      * $_GET
      * $_POST
      * $_COOKIE
      * $_FILES
      * $_SERVER
      *
      * @return ServerRequestInterface
      */
    public static function fromGlobals()
    {
        //.... skipped code
    
        $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER);
    
        return $serverRequest
            ->withCookieParams($_COOKIE)
            ->withQueryParams($_GET)
            ->withParsedBody($_POST)
            ->withUploadedFiles(self::normalizeFiles($_FILES));
    }
    

    (ServerRequestInterface on Github)

    The other methods are to consume the same data:

    • getServerParams
    • getCookieParams
    • getQueryParams
    • getParsedBody
    • getAttributes
    • getAttribute

    So we have “setter” and matching “getter” methods.

    What’s up with the “getAttributes” and “getAttribute” methods? This methods are important for the next part of our blog series. The Middleware.

  • PSR-7 Standard  – Part 5  – HTTP Client

    PSR-7 Standard – Part 5 – HTTP Client

    This post is part of series:


    The fifth part of the PSR-7 series describes the HTTP Client.

    The HTTP client is tool which sends a request to a server and returns the response.

    Sadly PSR-7 does not contain an interface for the HTTP client. The Standard contains only the HTTP messages. The client itself is part of the proposed PSR-18.

    PSR-18 is very small. It contains only an Interface with one method and some exception classes. The important part is the sendRequest method. It is easy as pie. A request has to be passed and the client should return a response or throw an ClientException exception.

    interface ClientInterface
    {
        /**
         * Sends a PSR-7 request and returns a PSR-7 response. 
         * 
         * @param RequestInterface $request
         *
         * @return ResponseInterface
         *
         * @throws \Psr\Http\Client\ClientException If an error happens during processing the request.
         */
        public function sendRequest(RequestInterface $request): ResponseInterface;
    }
    

    At the moment we cannot use PSR-18 in a production environment until it is accepted by the FIG. Fortunately there is an other project which tries to close the gap until the PSR-18 is accepted. The project is the father of the PSR-18. We talk about the HTTPPlug Project (former PHP-HTTP).

    HTTPPlug Project

    The project’s homepage is http://httplug.io/. The main idea is to decouple a PHP package/library from implementation by providing a HTTP client abstraction and some standard implementations.

    It provides a Composer meta package. You don’t know what a Composer meta package is? Think of it as a kind of interface for packages.

    This meta package can be added as requirements in your composer.json. Any compatible package can “provide” an implementation for the meta package.

    If you need a HTTP Client Implementation in your project, you should relay on the meta package instead of an real implementation.

    The implementation can be fulfilled in a project where the PHP library should be used. It is possible to pick any implementation from a huge list of existing implementations.

    An Example Library

    First we need a composer.json which requires a client implementation. For testing purposes we can add a development requirement to add some example scripts to the library. The HTTP-Plug project comes with a small and handy CURL based implementation which provides an implementation for the meta package. For the message implementation i.e. the Request, we make use of Guzzle’s PSR-7 implementation (see previous posts).

    {
      "name": "acme/my-super-php-library",
      "require": {
        "php-http/client-implementation": "^1.0",
        "guzzlehttp/psr7": "^1.4"
      },
      "autoload": {
        "psr-4": {
          "Acme\\MyLibrary\\": "src"
        }
      },
      "require-dev": {
        "php-http/curl-client": "^1.7"
      }
    }
    

    After a composer install  we are ready to create a sample service which uses the HTTP client. Let’s try to fetch the XML feed from dev98.de.

    We create a service class to fetch the XML feed. The service has a dependency to the HTTP Client interface. The file should be placed in the “src” sub-directory according to our PSR4 autoloading setting in composer.json file.

    <?php
    
    namespace Acme\MyLibrary;
    
    use GuzzleHttp\Psr7\Request;
    
    class FetchXmlFeedService
    {
        /**
         * @var \Http\Client\HttpClient
         */
        private $client;
    
        /**
         * ExampleService constructor.
         * @param \Http\Client\HttpClient $client
         */
        public function __construct(\Http\Client\HttpClient $client)
        {
            $this->client = $client;
        }
    
        /**
         * @return \SimpleXMLElement
         * @throws \Exception
         */
        public function fetch(): \SimpleXMLElement
        {
            $request = (new Request('GET', 'https://dev98.de/feed/'))
                ->withHeader('Accept', 'application/xml');
    
            $response = $this->client->sendRequest($request);
    
            if ($response->getStatusCode() !== 200) {
                throw new \Exception('Could not fetch XML feed');
            }
    
            return simplexml_load_string($response->getBody()->getContents());
        }
    }

    The service does not rely on a real implementation. The \Http\Client\HttpClient  is only an interface.

    Now we create a sample script which calls our service in the sub-directory “examples”.

    <?php
    
    require_once __DIR__ . '/../vendor/autoload.php';
    
    $httpClient = new \Http\Client\Curl\Client();
    $service = new \Acme\MyLibrary\FetchXmlFeedService($httpClient);
    $xmlData = $service->fetch();
    
    foreach ($xmlData->channel->item as $item) {
        echo (string) $item->title . "\n";
    }
    

    That’s our simple library with an example script. Testing this library is also not a big deal. We can easily mock the Interface.

    Use the Library

    If we want to use the library in a real project, we need to add the library in our project’s composer.json file. Then we add an implementation for the http client. This must not be the same as the development requirements in our library. This is a big advantage. If our PHP framework comes with an implementation which fits, we can re-use it.

    The complete source of the blog post is available on Github: https://github.com/cmuench/psr7-example-library

    The next blog post in this series describes the Server-Request.

  • Chrome 63 vs. dev domains

    Chrome 63 vs. dev domains

    With the recent update to version 63 for Chrome they introduced a feature, that redirects all *.dev  calls from HTTP to HTTPS.

    If you are using *.dev  domains to access your development machine, this behaviour is probably not what you want for all your local urls.

    Configuring dnsmasq

    At netz98 we are using dnsmasq to redirect all *.dev  calls to our localhost.

    dnsmasq has a setting where you can easily change this to another domain like *.localhost .

    First of locate the config file for dnsmasq. If you have installed it using brew you will probably find it under /usr/local/etc/dnsmasq.conf .

    Open the file in an editor of your choice and look for the following line:

    address=/dev/127.0.0.1

    And change it to:

    address=/localhost/127.0.0.1

    If you cannot find a line setting the address like shown above, just add a new line with the setting.

    To restart dnsmasq you can use

    brew services restart dnsmasq

    Configuring apache / nginx

    Depending on your choice of setup you might be using an nginx or apache setup.

    You most likely have some virtual-hosts or server settings that might need to be adjusted to the new domain.

    This could look something like this for nginx.

    server {
      listen 80;
    
      server_name shop.dev;
    
      // ....
    }

    Locate the server_name setting and any other occurences of your *.dev  domain and change it to .localhost

    Do not forget to change your Magento1 or Magento2 Urls in the core_config_data table.

    MacOS Nameserver

    For Firefox and Safari and a working ping foo.bar.localhost , you also have to add a setting for that.

    Create the file /etc/resolver/localhost  with the following content:

    nameserver 127.0.0.1

    After doing so, you need to restart the DNS Resolver and flush caches.
    I am on the latest MacOS 10.13 so I had to run the following command:

    sudo killall -HUP mDNSResponder; sudo killall mDNSResponderHelper; sudo dscacheutil -flushcache

    If you are running a different MacOS version, you can find a great blog post over at Dreamhost where they list the commands for each MacOS version: https://help.dreamhost.com/hc/en-us/articles/214981288-Flushing-your-DNS-cache-in-Mac-OS-X-and-Linux

    UPDATE:
    You might have to do a reboot of your machine so that the resolver works.

    UPDATE 2:
    Firefox and Safari require the above mentioned resolver to work.

    If you got anything to add or there even is a better solution feel free to leave a comment below.

  • PSR-7 Standard  – Part 4 – File Uploads

    PSR-7 Standard – Part 4 – File Uploads

    This post is part of series:


    After we learned what a Request and a Response are, let’s now look how we can send files to the server. Then have a look on how we can process them with Guzzle on the server side.

    Client Side Script

    As you can see in the diagram, a file upload is also handled as stream.

    First we create script “file_upload.php” with this content which initializes the autoloader and creates a sample file for the upload test.

    <?php
    require_once 'vendor/autoload.php';
      
    // create a test file
    file_put_contents('foo.txt', '"Foo" is the content of the file');
    

    After we have a text file, we can create our stream with the Guzzle PSR-7 component. Please add the code to the existing file.

    // put test file into multipart stream
    $multipart = new \GuzzleHttp\Psr7\MultipartStream([
        [
            'name' => 'upload_file',
            'contents' => fopen('foo.txt', 'r')
        ],
    ]);
    

    The MultipartStream gives us the ability to send more than once file to the server. As last part of the script we need to create a Request to send the MultipartStream to the server.

    $request = new \GuzzleHttp\Psr7\Request('POST', 'http://127.0.0.1:8080');
    $request = $request->withBody($multipart);
    
    $client = new \GuzzleHttp\Client();
    $response = $client->send($request);
    echo $response->getBody();

    Server Side Script

    Please create the script “server_file.php” to receive the files.

    <?php
    
    require_once __DIR__ . '/vendor/autoload.php';
    
    $request = \GuzzleHttp\Psr7\ServerRequest::fromGlobals();
    $files = $request->getUploadedFiles();
    
    $response = new \GuzzleHttp\Psr7\Response();
    $response = $response->withStatus(200, 'OK');
    
    $uploadedFiles = $request->getUploadedFiles();
    
    $uploadedFileInfos = [];
    foreach ($uploadedFiles as $uploadedFile) {
        /** @var $uploadedFile \GuzzleHttp\Psr7\UploadedFile */
        $uploadedFileInfos[] = [
            'file_name' => $uploadedFile->getClientFilename(),
            'mime_type' => $uploadedFile->getClientMediaType(),
            'size'      => $uploadedFile->getSize(),
            'content'   => (string) $uploadedFile->getStream()
        ];
    }
    
    $response = $response->withBody(
        \GuzzleHttp\Psr7\stream_for(print_r($uploadedFileInfos))
    );
    
    echo \GuzzleHttp\Psr7\str($response);
    

    The script is very simple. It creates a ServerRequest (we talk about this in a future blog post). The ServerRequest can handle the MultipartStream and return the files with the handy method method getUploadedFiles. This methods is now a standard to get all data of the uploaded files. If you know how files are handled without PSR-7 you know that this is a really enhancement.

    For debugging we collect all data of the uploaded files and return them back as response to the client.

    Test the Upload

    Run the server in console:

    php -S 127.0.0.1:8080 server_file.php

    Run the client script in a second console:

    php file_upload.php

    If you did everything correct you should now see the result of the server script:

    HTTP/1.1 200 OK
    
    Array
    (
        [0] => Array
            (
                [file_name] => foo.txt
                [mime_type] => text/plain
                [size] => 32
                [content] => "Foo" is the content of the file
            )
    
    )

     

    That’s it. The next blog post will give you some inside into the HTTP Client.

  • Run Mailhog in Docker and use it in PHP

    Run Mailhog in Docker and use it in PHP

    This post describes how you can install and configure Mailhog as SMTP Server for your local PHP development environment. This is useful to catch all outgoing emails.

    A running PHP and Docker environment is required to follow the instructions.

    Install Mailhog

    On my local machine, I have docker-compose.yml file which contains a lot of services (e.g MySQL, Elastic, Redis) which I use during the daily development.

    For our mailhog example we need only one service. Please create a docker-compose.yml with this content:

    version: '2'
    services:
        mailhog:
            container_name: mailhog
            image: mailhog/mailhog
            restart: always
            ports:
                - 1025:1025
                - 8025:8025

    Run docker-compose up -d mailhog to create and start the container. If the mailhog image does not exist, Docker will start to download the image from official Docker-Hub.

    Verify if everything is up and running.

    docker-compose ps
    Name      Command             State    Ports
    -------------------------------------------------------------------------------------
    mailhog   MailHog             Up       0.0.0.0:1025->1025/tcp, 0.0.0.0:8025->8025/tcp
    

    No you can use the TCP Port 1025 for sending email over SMTP protocol. The Port 8025 contains the Web-UI.

    Configure PHP

    Out goal is that PHP’s intern command “mail” uses our freshly installed Mailhog server. To effect this, we need to set the “sendmail_path” setting.

    Please modify your php.ini file and set the following directive:

    sendmail_path = docker exec -i mailhog sendmail -S localhost:1025

    From now, every “mail” command call uses the docker container with the name “mailhog” to send any email.

    We can test this with a simple PHP CLI call:

    php -r 'mail("foo@example.com", "test", time(), "From: Mailhog <mailhog@example.com>");'

    We should now be able to see the arrived message in the Web-UI. Open your browser with the address “http://localhost:8025”.

  • PSR-7 Standard  – Part 3  – Response

    PSR-7 Standard – Part 3 – Response

    This post is part of series:


    In the last blog post we described the RequestInterface of PSR-7. Every application will process this request and returns a response to the calling client. The response is the part where a backend sends a result of an server operation back to the client. Let’s view how the ResponseInterface is designed.

    /**
     * Representation of an outgoing, server-side response.
     *
     * Per the HTTP specification, this interface includes properties for
     * each of the following:
     *
     * - Protocol version
     * - Status code and reason phrase
     * - Headers
     * - Message body
     *
     * Responses are considered immutable; all methods that might change state MUST
     * be implemented such that they retain the internal state of the current
     * message and return an instance that contains the changed state.
     */
    interface ResponseInterface extends MessageInterface
    {
        /**
         * Gets the response status code.
         *
         * The status code is a 3-digit integer result code of the server's attempt
         * to understand and satisfy the request.
         *
         * @return int Status code.
         */
        public function getStatusCode();
    
        /**
         * Return an instance with the specified status code and, optionally, reason phrase.
         *
         * If no reason phrase is specified, implementations MAY choose to default
         * to the RFC 7231 or IANA recommended reason phrase for the response's
         * status code.
         *
         * This method MUST be implemented in such a way as to retain the
         * immutability of the message, and MUST return an instance that has the
         * updated status and reason phrase.
         *
         * @link http://tools.ietf.org/html/rfc7231#section-6
         * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
         * @param int $code The 3-digit integer result code to set.
         * @param string $reasonPhrase The reason phrase to use with the
         *     provided status code; if none is provided, implementations MAY
         *     use the defaults as suggested in the HTTP specification.
         * @return static
         * @throws \InvalidArgumentException For invalid status code arguments.
         */
        public function withStatus($code, $reasonPhrase = '');
    
        /**
         * Gets the response reason phrase associated with the status code.
         *
         * Because a reason phrase is not a required element in a response
         * status line, the reason phrase value MAY be null. Implementations MAY
         * choose to return the default RFC 7231 recommended reason phrase (or those
         * listed in the IANA HTTP Status Code Registry) for the response's
         * status code.
         *
         * @link http://tools.ietf.org/html/rfc7231#section-6
         * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
         * @return string Reason phrase; must return an empty string if none present.
         */
        public function getReasonPhrase();
    }
    

    Like the request, the response has to be build in an immutable way. This means that every method returns a new instance of the object instead of a reference to the existing object. The interface is a lot simpler than the RequestInterface. It contains only one method withStatus to pass a HTTP status code. All the other methods are inherited by MessageInterface which we already know from RequestInterface. This is consequent, because the Request and Response are communicating trough messages. So the Request sends a message and the Response returns a message.

    Using Guzzle

    Like in the previous article we make use of the PSR7 library of the Guzzle project.

    The installation is easy as:

    composer.phar require guzzlehttp/guzzle:~6.0

    We replace the “server.php” of the previous article with this code:

    <?php
    require_once __DIR__ . '/vendor/autoload.php';
    
    $response = new \GuzzleHttp\Psr7\Response();
    $response = $response->withStatus(200, 'OK');
    $response = $response->withBody(
        \GuzzleHttp\Psr7\stream_for(
            print_r($_SERVER, true)
        )
    );
    
    echo \GuzzleHttp\Psr7\str($response);

    The script is the equivalent of the server.php script of previous article where we simply dumped the $_SERVER array. Not it is HTTP compliant.

    We build a new response with a status code 200 and “OK” as phrase for the HTTP status header line. According to the RequestInterface we must pass a stream as parameter. To simply convert a string we can use the stream_for function of the Guzzle library.

    At the end we need to create a valid HTTP message string which can simply pushed to the client which PHP’s echo function. Guzzle provides the handy str function to convert an instance of MessageInterface to a string. We remember that the ResponseInterface extends the MessageInterface. So we can use the function and let Guzzle do the boring part.

    Let’s test the server with our previous generated client.php script. Before we can test, we start the server.php with the internal PHP HTTP server:

    php -S 127.0.0.1:8080 server.php

    Now run the client in a second console:

    php client_guzzle.php

    We should now the the output of our server:

    HTTP/1.1 200 OK
    
    Array
    (
        [DOCUMENT_ROOT] => /Users/cmuench/Desktop/psr-7
        [REMOTE_ADDR] => 127.0.0.1
        [REMOTE_PORT] => 57891
        [SERVER_SOFTWARE] => PHP 7.0.22 Development Server
        [SERVER_PROTOCOL] => HTTP/1.1
        [SERVER_NAME] => 127.0.0.1
        [SERVER_PORT] => 8080
        [REQUEST_URI] => /mypath?foo=bar&baz=zoz
        [REQUEST_METHOD] => GET
        [SCRIPT_NAME] => /mypath
        [SCRIPT_FILENAME] => server.php
        [PHP_SELF] => /mypath
        [QUERY_STRING] => foo=bar&baz=zoz
        [HTTP_HOST] => 127.0.0.1:8080
        [HTTP_USER_AGENT] => GuzzleHttp/6.2.1 curl/7.54.0 PHP/7.0.22
        [REQUEST_TIME_FLOAT] => 1508404883.1422
        [REQUEST_TIME] => 1508404883
    )

    In the next article we try to upload a file with the client to the server.