Manifest.json

My wife’s Japanese comic about our family is a responsive website.

A cool trick I learned at ConFoo while listening to Christian Heilmann speak was that I could leverage built-in mobile technology by simply adding a manifest.json file to the code.

A manifest turns a responsive website into an installable app. It lets users add it on their mobile phone’s home screen. When they launch the site it gets a splash screen and runs in full screen mode, basically behaving like a native app.

Caveat: For this to work HTTPS is required. Use certbot if you don’t already.

I used Manifest Generator to get started and it was easy. According to the ConFoo talk Bing indexes sites with manifest.json files and prioritizes them as smartphone compatible. A simple SEO win?

Now my family’s manga is an app. Horray for the open web!

Building a Simple API using Opulence PHP

This tutorial will show you how to code a simple JSON API using Opulence PHP. We will install Opulence’s skeleton project using composer, then create a ‘user’ database entity, and finally we will match CRUD (Create, Read, Update, Delete) to POST, GET, PUT, and DELETE.

Prerequisites: PHP7, Composer, MySQL.

Installing

Create an Opulence project with the following command:

composer create-project opulence/project SimpleApi --prefer-dist 

The default Opulence app name is Project. Using apex, rename it to SimpleApi.

cd SimpleApi
php apex app:rename Project SimpleApi

This command will output:

JSON Config

According to the documentation if a client does not request JSON then HTML will be returned. This is “the right way to do it” but for the sake of this API we always want to return JSON. We can do this by adding the following code to config/http/views.php:

if (!isset($_SERVER['CONTENT_TYPE'])) {
    $_SERVER['CONTENT_TYPE'] = 'application/json';
}

We also want to disable the default Session and Csrf middlewares because REST clients do not (always) work with cookies. Open config/http/middleware.php and comment out:

return [
    CheckMaintenanceMode::class,
//    Session::class,
//    CheckCsrfToken::class
];

Database Config

Out of the box PostgreSQL is the default database driver. To use MySQL change line ~5 in src/SimpleApi/Application/Bootstrappers/Databases/SqlBootstrapper.php from PostgreSQL to:

use Opulence\Databases\Adapters\Pdo\MySql\Driver;

Manually create a MySQL database named simpleapi and modify config/environment/.env.app.php accordingly.

Environment::setVar('DB_HOST', 'localhost');
Environment::setVar('DB_USER', 'root');
Environment::setVar('DB_PASSWORD', 'root');
Environment::setVar('DB_NAME', 'simpleapi');
Environment::setVar('DB_PORT', 3306);

Database Entity

Create a user table with the following columns: [ id (primary), email (unique), firstname (string), lastname (string), age (integer, optional) , country (2 character string) ]

CREATE TABLE `user` (
  `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `email` varchar(255) NOT NULL UNIQUE,
  `firstname` varchar(255) NOT NULL,
  `lastname` varchar(255) NOT NULL,
  `age` int,
  `country` char(2) NOT NULL
);

Using apex, create a matching entity class named User.

php apex make:entity User

Note: These commands create stubs / empty templates. You must finish the code yourself!

Open the newly created src/SimpleApi/User.php and finish the mutator methods so that the properties match the database table. Implement JsonSerializable too.

<?php
namespace SimpleApi;

use Opulence\Orm\IEntity;

class User implements IEntity, \JsonSerializable
{
    /** @var int */
    private $id;

    /** @var string */
    private $email;

    /** @var string */
    private $firstname;

    /** @var string */
    private $lastname;

    /** @var int|null */
    private $age;

    /** @var string */
    private $country;

    public function getId(): int
    {
        return (int)$this->id;
    }

    public function setId($id): self
    {
        $this->id = $id;

        return $this;
    }

    public function getEmail(): string
    {
        return $this->email;
    }

    public function setEmail($email): self
    {
        $this->email = $email;

        return $this;
    }

    public function getFirstname(): string
    {
        return $this->firstname;
    }

    public function setFirstname($firstname): self
    {
        $this->firstname = $firstname;

        return $this;
    }

    public function getLastname(): string
    {
        return $this->lastname;
    }

    public function setLastname($lastname): self
    {
        $this->lastname = $lastname;

        return $this;
    }

    /**
     * @return int|null
     */
    public function getAge()
    {
        return $this->age;
    }

    public function setAge($age): self
    {
        $this->age = $age;

        return $this;
    }

    public function getCountry(): string
    {
        return $this->country;
    }

    public function setCountry($country): self
    {
        $this->country = $country;

        return $this;
    }

    public function jsonSerialize() : array
    {
        return [
            'id' => (int)$this->getId(),
            'email' => $this->getEmail(),
            'firstname' => $this->getFirstname(),
            'lastname' => $this->getLastname(),
            'age' => is_null($this->getAge()) ? null : (int)$this->getAge(),
            'country' => $this->getCountry(),
        ];
    }
}

Open src/SimpleApi/Application/Bootstrappers/Orm/OrmBootstrapper.php and register an ID generator for \SimpleApi\User

private function registerIdGenerators(IIdGeneratorRegistry $idGeneratorRegistry)
{
    // Register your Id generators for classes that will be managed by the unit of work
    $idGeneratorRegistry->registerIdGenerator(
        \SimpleApi\User::class,
        new \Opulence\Orm\Ids\Generators\IntSequenceIdGenerator('user_id_seq')
    );
}

Database Mapper

Using apex, create a SQL data mapper named User. When prompted pick SQL data mapper and use \SimpleApi\User as the entity.

php apex make:datamapper User

This command will output:

Open the newly created src/SimpleApi/Infrastructure/Orm/User.php and finish the stubs.

<?php
namespace SimpleApi\Infrastructure\Orm;

use Opulence\Orm\DataMappers\SqlDataMapper;
use Opulence\Orm\OrmException;

class User extends SqlDataMapper
{
    /**
     * Adds an entity to the database
     *
     * @param \SimpleApi\User $user The entity to add
     * @throws OrmException Thrown if the entity couldn't be added
     */
    public function add($user)
    {
        $statement = $this->writeConnection->prepare(
            'INSERT INTO user (email, firstname, lastname, age, country)
             VALUES (:email, :firstname, :lastname, :age, :country)'
        );
        $statement->bindValues([
            'email' => $user->getEmail(),
            'firstname' => $user->getFirstname(),
            'lastname' => $user->getLastname(),
            'age' => $user->getAge(),
            'country' => $user->getCountry(),
        ]);
        $statement->execute();
    }

    /**
     * Deletes an entity
     *
     * @param \SimpleApi\User $user The entity to delete
     * @throws OrmException Thrown if the entity couldn't be deleted
     */
    public function delete($user)
    {
        $statement = $this->writeConnection->prepare('DELETE FROM user WHERE id = :id');
        $statement->bindValues([
            'id' => [$user->getId(), \PDO::PARAM_INT]
        ]);
        $statement->execute();
    }

    /**
     * Gets all the entities
     *
     * @return \SimpleApi\User[] The list of all the entities
     */
    public function getAll() : array
    {
        $sql = 'SELECT * FROM user';

        return $this->read($sql, [], self::VALUE_TYPE_ARRAY);
    }

    /**
     * Gets the entity with the input Id
     *
     * @param int|string $id The Id of the entity we're searching for
     * @return \SimpleApi\User The entity with the input Id
     * @throws OrmException Thrown if there was no entity with the input Id
     */
    public function getById($id): \SimpleApi\User
    {
        $sql = 'SELECT * FROM user WHERE id = :id';
        $parameters = [
            'id' => [$id, \PDO::PARAM_INT]
        ];

        return $this->read($sql, $parameters, self::VALUE_TYPE_ENTITY, true);
    }

    /**
     * Saves any changes made to an entity
     *
     * @param \SimpleApi\User $user The entity to save
     * @throws OrmException Thrown if the entity couldn't be saved
     */
    public function update($user)
    {
        $statement = $this->writeConnection->prepare(
            'UPDATE user SET email = :email, firstname = :firstname, lastname = :lastname,
             age = :age, country = :country
             WHERE id = :id'
        );
        $statement->bindValues([
            'email' => $user->getEmail(),
            'firstname' => $user->getFirstname(),
            'lastname' => $user->getLastname(),
            'age' => $user->getAge(),
            'country' => $user->getCountry(),
            'id' => [$user->getId(), \PDO::PARAM_INT]
        ]);
        $statement->execute();
    }

    /**
     * Loads an entity from a hash of data
     *
     * @param array $hash The hash of data to load the entity from
     * @return \SimpleApi\User The entity
     */
    protected function loadEntity(array $hash): \SimpleApi\User
    {
        $entity = new \SimpleApi\User();

        $entity->setId($hash['id']);
        $entity->setEmail($hash['email']);
        $entity->setFirstname($hash['firstname']);
        $entity->setLastname($hash['lastname']);
        $entity->setAge($hash['age']);
        $entity->setCountry($hash['country']);

        return $entity;
    }
}

Controller Creation

Using apex, create a Controller named User. When prompted pick REST controller.

php apex make:controller User

This command will output:

Open the newly created src/SimpleApi/Application/Http/Controllers/User.php and finish the stubs. Type-hint any objects your controller needs in the controller’s constructor. Create a generic repository object for \SimpleApi\User.

<?php
namespace SimpleApi\Application\Http\Controllers;

use Opulence\Http\HttpException;
use Opulence\Http\Responses\JsonResponse;
use Opulence\Http\Responses\Response;
use Opulence\Orm\OrmException;
use Opulence\Orm\Repositories\Repository;
use Opulence\Orm\IUnitOfWork;
use Opulence\Routing\Controller;

class User extends Controller
{
    /** @var \Opulence\Orm\UnitOfWork */
    protected $unitOfWork;

    /** @var Repository */
    protected $repo;

    public function __construct(\SimpleApi\Infrastructure\Orm\User $dataMapper, IUnitOfWork $unitOfWork)
    {
        $this->unitOfWork = $unitOfWork;

        $this->repo = new Repository(
            \SimpleApi\User::class,
            $dataMapper,
            $this->unitOfWork
        );
    }

    /**
     * Creates a entity
     *
     * @return Response The response
     */
    public function create() : Response
    {
        $json = $this->request->getJsonBody();

        $user = new \SimpleApi\User();

        $user
            ->setEmail($json['email'])
            ->setFirstname($json['firstname'])
            ->setLastname($json['lastname'])
            ->setCountry($json['country']);

        if (isset($json['age'])) {
            $user->setAge($json['age']);
        }

        $this->repo->add($user);
        $this->unitOfWork->commit();

        return new JsonResponse($user);
    }

    /**
     * Deletes an entity
     *
     * @param mixed $id The Id of the entity
     * @return Response The response
     */
    public function delete($id) : Response
    {
        $user = $this->repo->getById($id);
        $this->repo->delete($user);
        $this->unitOfWork->commit();

        return new JsonResponse($user, 204);
    }

    /**
     * Shows an entity
     *
     * @param mixed $id The Id of the entity
     * @return Response The response
     * @throws HttpException
     */
    public function show($id) : Response
    {
        try {
            $user = $this->repo->getById($id);
        } catch (OrmException $e) {
            throw new HttpException(404);
        }

        return new JsonResponse($user);
    }

    /**
     * Shows all the entities
     *
     * @return Response The response
     */
    public function showAll() : Response
    {
        $user = $this->repo->getAll();

        return new JsonResponse($user);
    }

    /**
     * Updates an entity
     *
     * @param mixed $id The Id of the entity
     * @return Response The response
     */
    public function update($id) : Response
    {
        $json = $this->request->getJsonBody();

        /** @var \SimpleApi\User $user */
        $user = $this->repo->getById($id);

        if (isset($json['email'])) {
            $user->setEmail($json['email']);
        }
        if (isset($json['firstname'])) {
            $user->setFirstname($json['firstname']);
        }
        if (isset($json['lastname'])) {
            $user->setLastname($json['lastname']);
        }
        if (isset($json['country'])) {
            $user->setCountry($json['country']);
        }
        if (isset($json['age'])) {
            $user->setAge($json['age']);
        }

        $this->unitOfWork->commit();

        return new JsonResponse($user);
    }
}

Open config/http/routes.php and configure CRUD routes to use the controller class.

$router->group(['controllerNamespace' => 'SimpleApi\Application\Http\Controllers'], function (Router $router) {
    $router->group(['path' => '/user'], function (Router $router) {
        $router->get('', 'User@showAll');
        $router->post('', 'User@create');
    });
    $router->group(['path' => '/user/:id'], function (Router $router) {
        $router->get('', 'User@show');
        $router->put('', 'User@update');
        $router->delete('', 'User@delete');
    });
});

Barring any typos you should now have a simple API. To run Opulence locally, use the following command:

php apex app:runlocally

Use a REST client to POST the following JSON to the API:

POST: http://localhost/user

{
    "email": "foo@dev.null",
    "firstname": "Joe",
    "lastname": "Smith",
    "age": 999,
    "country": "JP"
}

Then try:

GET: http://localhost/user
GET: http://localhost/user/1
PUT: http://localhost/user/1
DELETE: http://localhost/user/1 

Got ideas on how to improve validation, error handling, security, or any other Opulence PHP tips? Post in the comments below.

Install PHP7 and Composer on Windows 10

PHP7 is a general purpose scripting language well suited for web development. Composer is the defacto package manager for PHP7. This tutorial will show you how to install PHP7 and Composer on Windows 10 for use in a command prompt.

A common misconception is that you need a web server like IIS, Apache, or Nginx to work with PHP7. In fact, PHP7 has it’s own built in web server that you can invoke at the command prompt. Modern PHP frameworks such as Opulence support this.

Installing PHP7

Download the latest PHP7 (non-thread safe version) zip file from http://windows.php.net/

Extract the contents of the zip file into C:\PHP7

Copy C:\PHP7\php.ini-development to C:\PHP7\php.ini

Open the newly copied C:\PHP7\php.ini in a text editor.

Scroll down to “Directory in which the loadable extensions (modules) reside.” and uncomment: extension_dir = “ext”

Notepad++ is great.

Scroll down to the DLL extensions section and uncomment the extensions you want to use.

My current setup.

Tweak other settings as needed.

Note: Don’t forget to keep your php.ini file in a safe place when you upgrade in the future!

Add C:\PHP7 to the Windows 10 system path environment variable.

Windows 10 has finally improved this interface, yay!

In a command prompt test that the installation is successful by typing php -v

ConEmu is great.

Installing Composer

On my computer I’ve created a C:\Users\dac514\bin directory where I keep miscellaneous executables. This directory is in my user path.

Use a search engine to find a tutorial and do something similar. Optionally install composer in the C:\PHP7 directory you just created as it’s already in your path.

To get composer.phar, drop to a command prompt, cd into your target directory, and run:

php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php composer-setup.php
php -r "unlink('composer-setup.php');"

(Important! Click here for a more secure and up-to-date install snippet.)

Next, create a new composer.bat file alongside the composer.phar file so that Windows 10 can execute it more easily. (Source)

echo @php "%~dp0composer.phar" %*>composer.bat

Test that it’s working by typing composer -V

ConEmu is still great.

MySQL Dump Full Structure, Partial Data, With Triggers & Routines.

You want to do a MySQL dump. You want the entire structure of the database but you want to exclude some tables because they are too big, have sensitive data, or other reasons. Your MySQL database has triggers, routines, and all that good stuff because it’s 2016.

When I went looking for a solution I read a tutorial that wrongly suggested dumping triggers and schema together in the first step. The problem with this approach is when you import your data, the ON INSERT triggers are executed, and this can lead to primary key conflicts or other weird issues. I learned the hard way.

A better way:

  • Schema first
  • Data next
  • Triggers and routines last
mysqldump --no-data --skip-triggers DATABASE > FILE.sql

mysqldump --no-create-db --no-create-info --skip-triggers --ignore-table=TABLE1--ignore-table=TABLE2 DATABASE >> FILE.sql

mysqldump --no-create-db --no-create-info --no-data  --routines --triggers --skip-opt DATABASE >> FILE.sql

Good times.

Autocomplete a Silex Project in PHPStorm

The problem with Silex, and Pimple in general, is that when you do:

$app = new \Pimple\Container();
$app['Foo'] = function () { return new \Acme\Foo(); };
$app['Bar'] = function () { return new \Acme\Bar(); };

PHPStorm has no way of knowing what’s going on in, or how to auto-complete, $app.

I’ve gotten around this in the past by creating an “Inception Proxy” alongside a .phpstorm.meta.php configuration but for a new Silex project I’ve inherited this is not possible.

Pro-tip: If your IDE doesn’t know what’s going on then neither will the poor jerks who inherit your code.

Looking for a solution to this I discovered the PHPStorm Silex Plugin. It’s a bit wonky but it does the job. (sometimes the IDE doesn’t recognize $app and I don’t know why yet.)

For the Silex Plugin to work it requires a manually created configuration file in the project root named “pimple.json”. This file more or less duplicates the functionality of .phpstorm.meta.php but I digress… Pimple.json can be automatically generated using Pimple Dumper.

The format of “pimple.json” looks like:

[
    {
        "name": "routes",
        "type": "class",
        "value": "Symfony\\Component\\Routing\\RouteCollection"
    },
    {
        "name": "request.http_port",
        "type": "int",
        "value": 80
    },
    {
        "name": "charset",
        "type": "string",
        "value": "UTF-8"
    }
]

Once that file is in place, and you jiggle the IDE/Plugin, auto-complete comes alive! Horray for sanity.

Password Protect WordPress Admin With .htaccess

The wp-admin panel is already password protected in that you are required to login. Sometimes that’s not good enough. This tutorial explains how to add an additional layer of authentication to the login process, essentially blocking wp-login.php requests from annoying bots or other malicious users.

Step 1:

Create a `/path/to/.htpasswd` file. (More info.)

Step 2:

Create a `/path/to/your/site/wp-admin/.htacess` file with the following content:

AuthUserFile /path/to/.htpasswd
AuthType basic
AuthName "Restricted Resource"
require valid-user

# Whitelists

<Files "admin-ajax.php" >
   Order allow,deny
   Allow from all
   Satisfy any
</Files>

<Files "*.css" >
   Order allow,deny
   Allow from all
   Satisfy any
</Files>

<Files ~ "\.(jpg|jpeg|png|gif)$">
   Order deny,allow
   Allow from all
   Satisfy any
</Files>

Change `/path/to/` your files accordingly.

Important! Under Whitelists I have added entries for admin-ajax.php, *.css, and a regular expression for images. This unblocks WordPress’ AJAX functionality used by certain plugins, as well as CSS and image files certain themes may be importing. Without these you risk breaking your site.

Step 3:

Append the following to your existing WordPress .htaccess file one parent folder up (Ie. /path/to/your/site/.htaccess):

<Files wp-login.php>
  AuthUserFile /path/to/.htpasswd
  AuthType basic
  AuthName "Restricted Resource"
  require valid-user
</Files>

Change `/path/to/` your files accordingly.

PHP Code Of Conduct, Discussion

« I note that after much hue and cry, and many arguments, I still do not know what color this bikeshed will be.

I feel I have been informed of the many examples of problems with colors, cultural relevance of specific hues, details of paint techniques, anecdotes of past experiences with varying colors, larger socio-economic issues reflected through color choices, philosophy of colors, philosophy *about* the philosophy of color, legal and moral issues confronted during color evaluation, the impact of other bikeshed color choices, and how specific colors (and patterns) are under-represented, the finer details of paint application personel selection, and how certain colors are representative of larger social issues being played out in microcosms in individual environments…

….but I still do not know what color this bikeshed will be.

Please advise. »

Source.

Fix CSS Path Errors by Setting Resource Root Folders In PHPStorm

While developing a plugin for WordPress I was having trouble linting CSS files in PHPStorm. One file in particular was giving hundreds of false positives for errors related to paths:

False positives
The right side gutter is all red but most of these errors are wrong.

This bothered me. I wanted to fix the reported errors but the reporting was wrong. Most of the time there was nothing to fix. After much fiddling I discovered files under a folder marked as Resource Root can be referenced as relative:

Resource Root
To fix the above, set Resource Root(s) in PHPStorm

Low and behold the errors became real! Oh crud, time to fix.

Real errors
Now the right side gutter shows actual errors (that I should fix!)

Source:

https://www.jetbrains.com/phpstorm/help/configuring-folders-within-a-content-root.html