Premessa: recentemente Google ha registrato il tld dev e pertanto non è più possibile utilizzarlo per sviluppare in locale, dato che i browser non lo risolvono più in http ma forzano l'utilizzo del protocollo https. Il presente post è stato modificato sostituendo dev con local, ovviamente il tutto funziona al meglio.
(aggiunto il 11-1-2018)

Piegare alle esigenze di sviluppo un sistema come Windows può essere faticoso con il rischio di avere una soluzione in definitiva poco pratica. L'obiettivo che ci poniamo è di poter sviluppare applicazioni web utilizzando un IDE come Eclipse o PhpStorm, su Windows ma senza una macchina virtuale come Vagrant o una Debian in VirtualBox (per altro entrambe molto consigliabili, anche se, avendo provato, le prestazioni sembrano pessime, soprattutto se i sorgenti sono montati dall'host). Inoltre vogliamo non dover mettere mano alla configurazione del webserver per ogni nuovo progetto. In ultimo sarebbe bello se il tutto fosse portabile e con la possibilità di usare git/svn e composer.

Niente in tutto ;)

Nel mio caso l'installazione è stata provata su Windows 7 64bit SP1 (ma dovrebbe funzionare anche su Windows 10) con tre schede di rete: una sulla WiFi, una sul cavo ethernet e una scheda su rete virtuale bridged con IP fisso 10.0.2.5.

Installazione (non-portable)

DNS

Ho testato due soluzioni: la prima è "Dual DHCP DNS Server" che per un motivo inconprensibile non si mette in ascolto su 127.0.0.1 ed è indicata se si ha almeno una scheda di rete con IP fisso. La seconda è "Acrylic DNS Proxy" che invece si è rivelato un po' più flessibile e si può impostare per mettersi in ascolto sulla localhost, utile quando il pc è inserito in una rete in dhcp.

Ma vediamo la prima: Dual DHCP DNS Server. Scaricare il programma e installarlo in C:\DualServer (come proposto). Modificare il file C:\DualServer\DualServer.ini nel modo seguente:

[SERVICES]
DNS
[LISTEN_ON]
10.0.2.5
[WILD_HOSTS]
*.local=127.0.0.1
*.cake=127.0.0.1
[FORWARDING_SERVERS]
8.8.8.8
8.8.8.4

Modificare le impostazioni tcp di tutte le schede di rete in modo che il dns punti all’indirizzo della scheda di rete con IP statico (nell'esempio 10.0.2.5, ma mettete il vostro indirizzo).

Avviare quindi il dns come servizio (assicurarsi che sia veramente così dalla gestione servizi) utilizzando il comando InstallService.exe nella cartella del DualServer. Per verificare che sia tutto corretto e che funzioni provare ad avviare il dns con il comando RunStandAlone.bat che apre una console con un efficace log di ciò che accade, aprire una console e pingare qualche dn, se risponde correttamente provare con qualcosa che finisca in .local o .cake: se pinga 127.0.0.1 vuol dire che funziona tutto.

Al posto di Dual possiamo installare "Acrylic DNS Proxy", altrettanto immediato ed efficace. Scaricate il programma e installatelo, modificare quindi il file di configurazione AcrylicHosts.txt e aggiungete le due righe

127.0.0.1    *.local
127.0.0.1    *.cake

Riavviate il servizio e verificate da cmd con il comando nslookup che risponda correttamente su qualche esempio:

>nslookup www.example.cake
Server:  UnKnown
Address:  127.0.0.1

Nome:    www.example.cake
Address:  127.0.0.1

>nslookup www.example.local
Server:  UnKnown
Address:  127.0.0.1

Nome:    www.example.local
Address:  127.0.0.1

>nslookup www.example.com
Server:  UnKnown
Address:  127.0.0.1

Risposta da un server non autorevole:
Nome:    www.example.com
Addresses:  2606:2800:220:1:248:1893:25c8:1946
          93.184.216.34

Web server

Per quanto riguarda il server web, la mia scelta ricade su Wnmp, basato su Nginx, PHP 7 e MariaDB.

Scaricare Wnmp.exe e installarlo in C:\Wnmp, per i requisiti minimi vedere la documentazione.

Avviare Wnmp e verificare che funzioni: se richiede .NET, scaricarlo e installarlo.

Se richiesto (da php), scaricare visual studio redistributable 2015 x86 e installarlo.

Nginx

Per la configurazione dei virtual host dinamici, uno per i siti "normali" (non cake) e uno per i siti basati su cakephp, modifichiamo il file C:\Wnmp\conf\nginx.conf nel modo seguente:

worker_processes  1;

error_log  logs/error.log;
pid        logs/nginx.pid;

events {
  # Max value 16384
  worker_connections  8192;
  # Accept multiple connections
  multi_accept on;
}

# Settings that affect all server blocks
http {
  include       php_processes.conf;
  include       mime.types;
  default_type  application/octet-stream;

  access_log  logs/access.log;

  sendfile on;

  keepalive_timeout  65;
  ssl_session_timeout 10m;
  ssl_protocols TLSv1.2 TLSv1.1 TLSv1 SSLv3;
  ssl_ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AES:RSA+3DES:!ADH:!AECDH:!MD5:!DSS; 
  ssl_prefer_server_ciphers on;
  gzip  on;

  #  _                 _ 
  # | | ___   ___ __ _| |
  # | |/ _ \ / __/ _` | |
  # | | (_) | (_| (_| | |
  # |_|\___/ \___\__,_|_|
  #
  server {
    listen 80;
    server_name ~^([^.]+\.)?([^.]+)\.local$;
    index index.php;
    set $basepath "html";
    set $domain $host;
    set $servername $domain;
    # check multi name domain to multi application
    if ($domain ~ "^([^.]+)\.([^.]+)\.local$") {
      set $subdomain $1;
      set $domain $2;
      set $rootpath "${domain}/${subdomain}/";
      set $servername "${subdomain}.${domain}.local";
    }
    # check one name domain for simple application
    if ($domain ~ "^([^.]+)\.local$") {
      set $domain $1;
      set $rootpath "${domain}/www/";
      set $servername "${domain}.local";
    }
    access_log "logs/${servername}.access.log";
    error_log "logs/local.error.log";
    root $basepath/$rootpath;
    # check file exist and send request string to index.php 
    location / {
      try_files $uri $uri/ /index.php?$args;
    }
    # allow execute all php files
    location ~ \.php$ {
      try_files      $uri =404;
      fastcgi_pass   php_processes;
      fastcgi_index  index.php;
      fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
      fastcgi_param  SERVER_NAME      "$host";
      include        fastcgi_params;
    }
    # disallow access to apache configs
    location ~ /\.ht {
      deny all;
    }
    # disallow access to git configs path
    location ~ /\.git {
      deny all;
    }
  }
  #            _        
  #   ___ __ _| | _____ 
  #  / __/ _` | |/ / _ \
  # | (_| (_| |   <  __/
  #  \___\__,_|_|\_\___|
  #
  server {
    listen 80;
    server_name ~^([^.]+\.)?([^.]+)\.cake$;
    index index.php;
    set $basepath "html";
    set $domain $host;
    set $servername $domain;
    # check multi name domain to multi application
    if ($domain ~ "^([^.]+)\.([^.]+)\.cake$") {
      set $subdomain $1;
      set $domain $2;
      set $rootpath "${domain}/${subdomain}/webroot/";
      set $servername "${subdomain}.${domain}.cake";
    }
    # check one name domain for simple application
    if ($domain ~ "^([^.]+)\.cake$") {
      set $domain $1;
      set $rootpath "${domain}/www/webroot/";
      set $servername "${domain}.cake";
    }
    access_log "logs/${servername}.access.log";
    error_log "logs/cake.error.log";
    root $basepath/$rootpath;
    # check file exist and send request string to index.php 
    location / {
      index  index.php index.html;
      if (-f $request_filename) { 
        break; 
      }
      if (!-f $request_filename) {
        rewrite ^/(.+)$ /index.php?url=$1 last;
        break;
      }
    }
    location ~ \.php$ {
      try_files      $uri =404;
      fastcgi_pass   php_processes;
      fastcgi_index  index.php;
      fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
      fastcgi_param  SERVER_NAME      $host;
      fastcgi_param  DEBUG            "1";
      include        fastcgi_params;
    }
    # disallow access to apache configs
    location ~ /\.ht {
      deny all;
    }
    # disallow access to git configs path
    location ~ /\.git {
      deny all;
    }
  }
  #  _                 _ _               _   
  # | | ___   ___ __ _| | |__   ___  ___| |_ 
  # | |/ _ \ / __/ _` | | '_ \ / _ \/ __| __|
  # | | (_) | (_| (_| | | | | | (_) \__ \ |_ 
  # |_|\___/ \___\__,_|_|_| |_|\___/|___/\__|
  #
  # Begin HTTP Server
  server {
    listen 80 default_server;
    server_name localhost;
    access_log logs/localhost_access.log;
    error_log logs/localhost_error.log;
    root html;
    index  index.php index.html index.htm;
    ## If no favicon exists return a 204 (no content error).
    location = /favicon.ico {
      try_files $uri =204;
      log_not_found off;
      access_log off;
    }
    ## Don't log robots.txt requests.
    location = /robots.txt {
      allow all;
      log_not_found off;
      access_log off;
    }
    ## Try the requested URI as files before handling it to PHP.
    location / {
      ## Regular PHP processing.
      location ~ \.php$ {
        try_files  $uri =404;
        fastcgi_pass   php_processes;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
        include        fastcgi_params;
      }
      ## Static files
      location ~* \.(?:css|gif|htc|ico|js|jpe?g|png|swf)$ {
        expires max;
        log_not_found off;
        tcp_nodelay off;
        open_file_cache max=1000 inactive=120s;
        open_file_cache_valid 45s;
        open_file_cache_min_uses 2;
        open_file_cache_errors off;
      }
      ## Keep a tab on the 'big' static files.
      location ~* ^.+\.(?:ogg|pdf|pptx?)$ {
        expires 30d;
        tcp_nodelay off;
      }
    } # / location
  }
  # End HTTP Server
}

Inoltre modifichiamo il file fastcgi_params commentando con un # la riga

#fastcgi_param  SERVER_NAME        $server_name;

In questo modo possiamo mettere i nostri progetti nella cartella c:\Wnmp\html distinguendoli per domain name ed eventualmente soluzione, ad esempio possiamo avere:

  • C:\Wnmp\html
    • progetto1
      • www
      • test
    • acme
      • web
        • webroot
      • altro

accessibili secondo la modalità

  • localhost ⟶ C:\Wnmp\html\
  • www.progetto1.local ⟶ C:\Wnmp\progetto1\www\
  • test.progetto1.local ⟶ C:\Wnmp\progetto1\test\
  • web.progetto2.cake ⟶ C:\Wnmp\acme\web\webroot\
  • altro.progetto2.local ⟶ C:\Wnmp\acme\altro\

Facciamo una prova e diamo sempre un'occhiata ai file di log, che fra l'altro sono distinti per progetto.

Nota: i progetti Cakephp, in virtù dell'impostazione

fastcgi_param  DEBUG            "1";

sono già impostati in modalità debug, pertanto il DebugKit dovrebbe già essere attivato e disponibile (l'iconcina della fetta di torta bianca su sfondo rosso in basso a destra nella pagina web), ma se non lo è controllate che sia caricato sqlite in php e che ci sia il db nella cartella tmp.

PHP

Attivare dal menu della GUI di Wnmp le estensioni di php: gd, sqlite, intl. Il numero di processi php da attivare è impostabile anch'esso da Wnmp: diciamo che un valore pari al numero di core del computer è adeguato.

MariaDB

Una volta avviato è lì che lavora... ah, le credenziali superuser di MariaDB sono root e password

Heidi

Scaricare e installare Heidi quale GUI Client per MariaDB, i parametri di connessione sono i soliti: localhost su porta 3306 con utente e password come detti.

Git

Scaricare l'installazione dal sito ufficiale e scaricare e installare il framework .NET 4.5 o successivo (come suggerito da git stesso) necessario per attivare la funzionalità ssh di git.

Installare git spuntando tutto ma, in uno dei passaggi successivi, preferire "Checkout as is, commit Unix-style line endings" ed assicurarsi che sia spuntato "Enable Git credential Manager".

Configurare git da una console:

$ git config --global user.name "John Doe"
$ git config --global user.email Questo indirizzo email è protetto dagli spambots. È necessario abilitare JavaScript per vederlo.

Andiamo nella directory utente e creiamo una chiave ssh da usare con github o gitlab:

$ cd
$ ssh-keygen -t rsa -C "Questo indirizzo email è protetto dagli spambots. È necessario abilitare JavaScript per vederlo." -b 4096
$ cat .ssh/id_rsa.pub

copiamo e incolliamo la chiave pubblica nel posto giusto su gitlab e possiamo cominciare a clonare qualche progetto.

Composer

Installare composer, come da manuale, direttamente nella cartella C:\Wnmp\php, ovvero da console:

> cd C:\Wnmp\php
> php -r "copy(' https://getcomposer.org/installer ', 'composer-setup.php');"
> php -r "if (hash_file('SHA384', 'composer-setup.php') === '669656bab3166a7aff8a7506b8cb2d1c292f042046c5a994c43155c0be6190fa0355160742ab2e1c88d40d5be660b410') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
> php composer-setup.php
> php -r "unlink('composer-setup.php');"

Aggiungere al sistema il percorso della directory di php "C:\Wnmp\php" dal Pannello di Controllo ⟶ Modificare le variabili d'ambiente relative al sistema ⟶ Variabili d'ambiente... ⟶ Variabili di sistema "Path" ⟶ Modifica... e aggiungere in coda al valore

;C:\Wnmp\php

Creiamo inoltre nella cartella C:\Wnmp\php il comando composer.bat per eseguire composer senza dover chiamare php e passargli il giusto composer.phar:

@echo Off
set wd=%~dp0
php %wd%composer.phar %*

Da una console di Windows verifichiamo che funzioni sia php che composer:

> C:\Users\max> php -v
PHP 7.0.8 (cli) (built: Jul  8 2016 01:08:03) ( NTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies
    with Zend OPcache v7.0.8, Copyright (c) 1999-2016, by Zend Technologies
    with Xdebug v2.4.0, Copyright (c) 2002-2016, by Derick Rethans

C:\Users\max> composer
   ______
  / ____/___  ____ ___  ____  ____  ________  _____
 / /   / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__  )  __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
                    /_/
Composer version 1.4.1 2017-03-10 09:29:45
[...]

Cakephp

Per creare un nuovo progetto cakephp con composer:

> cd C:\Wnmp\html\acme
> composer create-project --prefer-dist cakephp/app foto

che sarà accessibile all'url  http://foto.acme.cake . Ovviamente, se sono richieste delle librerie aggiuntive per php che abbiamo dimenticato di attivare, basta andare in Wnmp spuntare quel che serve e dare una riavviatina al solo php. Se si sperimenta un certo appesantimento, soprattutto nell'ispezione del DebugKit, è opportuno effettuare un'operazione per rendere i file di asset servibili direttamente dal web server, piuttosto che da php. Nella root del progetto digitare:

> bin\cake plugin assets symlink

e leggere bene quali plugin vengono linkati: se il DebugKit non compare, è perché non risulta installato (anche se lo è). Occorre editare il file config\bootstrap.php, commentare temporaneamente il test che circonda il caricamento del plugin

// Only try to load DebugKit in development mode
// Debug Kit should not be installed on a production system
#if (Configure::read('debug')) {
  Plugin::load('DebugKit', ['bootstrap' => true, 'routes' => true]);
#}

e ripetere il comando, magari forzando la cosa:

> bin\cake plugin assets symlink DebugKit

quindi ripristinare il codice del file di bootstrap (e magari controllare in webroot che ci sia la cartella "debug_kit").