Saturday, November 18, 2017

An Example of PHP 7 For Windows 10 (IIS)

Hi! I just revisited PHP after a long break, and I learned some new tricks. The first trick was a package manager for Windows called Cholocatey. If you follow the instructions at https://chocolatey.org/install, you should have a working copy. I needed to run the Command Prompt "as administrator" to get the DOS command to work.

Then I ran the following commands:

choco install php

choco install composer

I normally use MongoDB for my database manager, so I needed the latest PHP driver. After some digging I downloaded the 64-bit non-thread-safe ZIP file from https://pecl.php.net/package/mongodb/1.3.2/windows. After expanding that file, I copied the DLL and  PDB to the C:\tools\php71\ext directory. I had to tell PHP about the extension in php.ini; that was achieved by adding the following line:

extension=php_mongodb.dll

Telling IIS about Chocolatey's PHP build was a bit trickier. I went into the IIS feature, then into Handler Mappings, and created the following module mapping...



I also wanted to use "index.php" files for home pages. That was achieved by going into Default Document in IIS, and adding that file name to the list.

To test this setup, I created a simple website that asked for a name, then displayed an updated alphabetized list of stored names. Since the HTML for such a form is trivial, I'll just give you the "action" file...

<!DOCTYPE html>
<!--
To change this license header, choose License Headers in Project Properties.
To change this template file, choose Tools | Templates
and open the template in the editor.
-->
<html>
    <head>
        <meta charset="UTF-8">
        <title>Updated List</title>
    </head>
    <body>
        <?php
        
        // put your code here
        include 'vendor/autoload.php';
        use MongoDB\Client as MongoDBClient;
        
        $client = new MongoDBClient('mongodb://localhost:27017');
        $x = $client->selectDatabase('project2')->listCollections();
        $collection = $client->project2->names;
        $firstName = filter_input(INPUT_POST, 'first_name');
        $lastName = filter_input(INPUT_POST, 'last_name');
        $inputOne = $collection->insertOne(['first_name' => "$firstName",
            'last_name' => "$lastName"]);
        echo "Added ", $lastName, ", ", $firstName, ".<br>\n" ;
  //      var_dump($inputOne);
       $result = $collection->find([], ['sort' => ['last_name' => 1,
           'first_name' => 1]]);
        echo '<ul>';
        foreach ($result as $value) {
            echo "<li>", $value['last_name'], ", ", $value['first_name'], 
                    "</li>";
        }
        echo '</ul>';
        
        ?>
    </body>
</html>

The "filter_input" function is one way to access POST form variables. Accessing $_POST entries directly has been deprecated. The "insertOne" method is how to add a single document to a particular collection. The second array in the "find" function is the the new way of sorting query results. This version alphabetizes by both last and first names in ascending order. Using an empty array in the first parameter tells MongoDB to retrieve the entire collection. Lastly, "vendor/autoload.php" is a script that allows the PHP engine to access the packages you installed using Composer. I just remembered to tell you... in order to use MongoDB in a PHP 7 site, you need to use the DOS command

composer require mongodb/mongodb 

at the site's root.


Tuesday, June 6, 2017

POST Requests and MongoDB Using PHP 7

Hi there! Captain Chaos is back with a brand new bag. A friend of mine wanted me to focus on "bread and butter" rather than Google arcana, so I took a detour into PHP 7. One nasty shock was learning Composer was unavoidable. That meant learning a bunch of new commands, new magic words, and configuration files. You can find installation instructions at https://getcomposer.org/doc/00-intro.md .

I decided to start with what I hoped was something simple... a POST form with nothing but text fields and a submit button, plus the equivalent of "SELECT *" without a WHERE clause. I like to avoid SQL and strings that should be checked by a compiler (and aren't) whenever possible, so I went with trusty-rusty MongoDB. I soon found out that installing a driver was just the beginning. So, I bit the bullet and installed Composer. After some googling and error, I learned the following spell:

composer require mongodb/mongodb

That conjured up a "composer.json" file and a whole directory of stuff. Fair enough. I started with a fairly simple-minded index.php...

<html>
    <head>
        <meta charset="UTF-8">
        <title>PHP Mongo Example</title>
    </head>
    <body>
        <form action="verify.php" method="post">
            <div>
                <label for="first_name">First name: </label>
                <input type="text" id="first_name" name="first_name" required>
            </div>
            <div>
                <label for="last_name">Last name:  </label>
                <input type="text" id="last_name" name="last_name" required>
            </div>
            <input type="submit" value="Continue">
        </form>
    </body>
</html>    

That gave me a functioning form, so so far so good. Next, I wrote a Name class (in the "name.php" file) with a function that will convert a Name to a string using just typecasting...

<?php

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
namespace php2;

/**
 * Description of Name
 *
 * @author child
 */
class Name {
    public $firstName = "";
    public $lastName = "";
 
    function __construct(string $first, string $last) {
        $this->firstName = $first;
        $this->lastName = $last;
    }
 
    function __toString() {
        return "$this->firstName $this->lastName";
    }
}

Notice that I used "__construct" rather than the class's name for the constructor. That seems to be a permanent fixture of PHP, but might come as a surprise to some. I don't know how old "__toString" is, but at least for PHP 7.0 it does the job. The gotcha here is a simple "include" statement won't access a user-defined class when using Composer. That meant casting this spell:

composer dump-autoload -o

I'll tell you now I use Windows, and that version of Composer installs an actual program, along with the "composer" command. You might need to use "php composer.phar" instead.

Lastly came the fun part, the "business logic". At first, I blindly used the old chestnut

$firstName = $_POST['first_name'];
$lastName = $_POST['last_name'];

Not so fast! That approach doesn't work in PHP 7. Instead, I learned the following from my IDE:

 $submitted = !is_null($_POST);
        if ($submitted) {
            $firstName = filter_input(INPUT_POST, 'first_name');
            $lastName = filter_input(INPUT_POST, 'last_name');

A lot more verbose, but it got the job done. Plus, instead of the old "include" statements, I needed:

        require 'vendor/autoload.php';
        use php2\Name;
        use MongoDB\Client;

Not as simpleminded, but more in line with other object-oriented languages. With the bullocks out of the way, things were fairly straightforward:

        $name = new Name($firstName, $lastName);
        echo 'Submitted ' . (string) $name . "<br />";
        $client = new Client();
        $collection = $client->php2->Names;
        $collection->insertOne(['first_name' => $name->firstName, 'last_name' => $name->lastName]);
        $cursor = $collection->find();
        echo '<ul>';
        foreach ($cursor as $document) {
            $foundName = new Name((string) $document['first_name'], (string) $document['last_name']);
            echo '<li>' . (string) $foundName . '</li>';
        }
        echo '</ul>';
        } else {
            echo 'Submission failed! <br />';
         
        }

For those familiar with MongoDB, this should be familiar; "php2" is the database and "Names" is the collection. For those unfamiliar with NoSQL, a collection is similar to a database table, except there's no schema. Pretty much anything goes. A "document" is like a table row, but it isn't constrained to any particular structure. Typically, a collection contains objects (documents) of the same class. You're not hallucinating; PHP 7 has actual data types. One nice thing I learned is I didn't need to specify the namespace in a "new" statement as long as there are no class name conflicts.

Well, that's all for now! This sorcerer's apprentice learned how to clean a floor. I hope you found this rambling useful. Cheerio!

Sunday, May 28, 2017

An Example of Flutter ListView and url_launcher

This mobile app reads a list of MP3 files that was encoded in JSON, and lists them on the cellphone's screen. Tapping on a list item triggers a website that plays that song back.

This Dart's dependencies were configured in pubspec.yaml as follows:

dependencies:
  http: any
  flutter:
    sdk: flutter
  url_launcher:

url_launcher is a Flutter plugin created by Google. My guess is they wanted to standardize mobile app browser launching. Here is an example of JSON this code consumes:

{"songs":["Doused.mp3","Peace_of_Mind.mp3","Siobhan.mp3","Slowdive - Souvlaki - 03 - 40 Days.mp3","When the Sun Hits.mp3"]}

Here is lib/main.dart, in all its glory:

import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

List<String> songFileNames = new List();

loadSongFileNames() async {
  http.Response response = await http.get('http://10.0.0.6:8080/api/filelist');
  String body = response.body;
  var json = JSON.decode(body);
  for (var song in json['songs']) {
    songFileNames.add(song);
  }
}

main() async {
  await loadSongFileNames();
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
//  final List<String> songFileNames = ['flutter.io', 'dartlang.org', 'golang.org'];
  List<ListTile> _getList() {
    List<ListTile> list = new List();
    for (var song in songFileNames) {
      ListTile tile = new ListTile(
        title: new Text(song),
        onTap: () {
          launch('http://10.0.0.6:8000/?song=$song');
        },


      );
      list.add(tile);
    }
    return list;
  }

  @override  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new Scaffold(
        appBar: new AppBar(title: new Text('My Songs'),),
        body: new ListView(
          children: _getList(),
        ),
      ),
    );
  }
}