Category Archives: AWS

How to use the Amazon AWS SDK for Textract with PHP 7.0 Asynchronously

A few days ago, I got an interesting question about my post which describes using the Amazon AWS SDK for Texttract. The question was “How can I d this with a PDF stored in S3? I know you need to use analyzeDocumentAsynch but unsure how to then get the results of the Asynch operation“.

It turns out to be pretty easy, once you’ve got the synchronous example running. The synchronous Textract example is described in that previous blog post.

Here are the code changes you need to make. Keep all the source code as before, but starting with the call to analyzeDocument, replace that and the following lines with this code:

$promise = $client->analyzeDocumentAsync($options);
$promise->then(
    // $onFulfilled
    function ($value) {
		echo 'The promise was fulfilled.';
		processResult($value);
    },
    // $onRejected
    function ($reason) {
        echo 'The promise was rejected.';
    }
);

// If debugging:
// echo print_r($result, true);
function processResult($result) {
	$blocks = $result['Blocks'];
	// Loop through all the blocks:
	foreach ($blocks as $key => $value) {
		if (isset($value['BlockType']) && $value['BlockType']) {
			$blockType = $value['BlockType'];
			if (isset($value['Text']) && $value['Text']) {
				$text = $value['Text'];
				if ($blockType == 'WORD') {
					echo "Word: ". print_r($text, true) . "\n";
				} else if ($blockType == 'LINE') {
					echo "Line: ". print_r($text, true) . "\n";
				}
			}
		}
	}
}

When you run your PHP code from the command line, you’ll notice a small wait while the asynchronous code processes, and then you’ll see the same output as before.

Here’s a link to the Guzzle Promises project to give you an idea of how to use Promises in PHP.

And here’s the full source example use of analyzeDocumentAsync.

If you found this interesting, click the subscribe button below! I write a new post about once a week.

How to access an AWS RDS using JDBC in your Android app – Part II

In my last post, I described a quick way to set up an Amazon MySQL RDS (Relational Database Service).

In this post, I’m going to build an Android app which uses JDBC to search that database, and list results.

Caveat: As I mentioned in my previous post, this is a “quick and dirty” way of doing things, and it’s not recommended to do things exactly this way. However, this method is fine when you’re building a proof of concept or a demo and you need to get things done quickly. It took me an afternoon to throw together a working demo using this method!

To get started on your app, fire up Android Studio and create a “New Project” with an “Empty Activity”. Accept all the defaults, but make sure your app is for Java (unless you want to work with Kotlin).

We just want to add a few simple items for the user interface: a text input for searching on a term, a button to submit the search term, and a scrollview that can be used to display results. Let’s do that now.

When I created my empty activity, a new layout file was added called activity_main.xml. I opened that up in the design view, and added the widgets that I wanted. Eventually, I finished the layout by customizing it in the text view. Here’s the final layout:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <EditText
        android:id="@+id/editText"
        android:layout_width="357dp"
        android:layout_height="48dp"
        android:ems="10"
        android:hint="Enter Search term and hit button for results"
        android:inputType="text"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="8dp"
        android:layout_marginRight="8dp"
        app:layout_constraintRight_toRightOf="parent"
        android:layout_marginLeft="8dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintHorizontal_bias="0.513"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"/>
    <Button
        android:id="@+id/btnSearch"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Search"
        android:layout_marginRight="8dp"
        app:layout_constraintRight_toRightOf="parent"
        android:layout_marginLeft="8dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintHorizontal_bias="0.502"
        app:layout_constraintTop_toBottomOf="@+id/editText"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginTop="8dp"/>

    <ScrollView
        android:id="@+id/scrollview"
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        android:layout_margin="8dp"
        app:layout_constraintBottom_toTopOf="@+id/textView"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btnSearch">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <TextView
                android:id="@+id/tvResults"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:text=""></TextView>
        </LinearLayout>
    </ScrollView>

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:text="Type in text, click a button to search"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

It looks like a lot, but it isn’t. Android layout files are quite verbose! One comment: notice that the ScrollView has a layout height of 0dp. It took me a few minutes of searching to figure out that this was necessary. Prior to doing that, the ScrollView results overlapped the search button and instructional text.

Notice that I’ve set Android @+ids for the parts that I need to access programmatically. I need to be able to click the search Button (@+id/btnSearch), get text from the input EditText (@+id/editText), and display text in the ScrollView‘s TextView (@+id/tvResults).

Next, I opened the MainActivity class, and added the methods needed to click the button, get results, and display them – like this:

package com.fullstackoasis.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.text.Editable;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity implements AsyncResponse {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button b = (Button)this.findViewById(R.id.btnSearch);
        b.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                searchByName();
            }
        });
    }

    protected void searchByName() {
        EditText et = (EditText)findViewById(R.id.editText);
        Editable editable = et.getText();
        String s = editable.toString();
        Log.d("MainActivity", "searchByName " + s);
        if (s.length() > 2) {
            MySQLAsyncTask mySQLAsyncTask = new MySQLAsyncTask();
            mySQLAsyncTask.setDelegate(this);
            mySQLAsyncTask.execute(s);
        } else {
            displayResults("Please type in at least 3 letters, for example 'Italian'");
        }
    }

    public void processFinish(String result) {
        if (result.length() > 502) {
            Log.d("MainActivity:", "processFinish " + result.substring(0, 500));
        } else {
            Log.d("MainActivity:", "processFinish " + result);
        }
        displayResults(result);
    }

    private void displayResults(String res) {
        TextView tvResults = (TextView)findViewById(R.id.tvResults);
        tvResults.setText(res);
    }
}

Now I only needed one more crucial bit, the Java class which contacts the Amazon RDS. I added a new Java class by clicking the menu item File > New > Java Class, and chose the name MySQLAsyncTask. I had it extend AsyncTask. The source for that class is shown next. If you copy this code for your own working demo, you will have to edit the url string to use your own RDS endpoint. Also, notice the big warnings about checking in files into source control if they contain hard-coded strings that would make your credentials publicly available! I’m not going to go into how to handle that here, but just don’t do it.

package com.fullstackoasis.myapplication;

import android.os.AsyncTask;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.PreparedStatement;

final class MySQLAsyncTask extends AsyncTask<String, Void, String> {
    private static final String url = "jdbc:mysql://healthdata-1.c84gpzpanfrn.us-east-1.rds.amazonaws.com:3306/food_inspections";
    private static final String user = "MY_WONDERFUL_USER_NAME"; // WARNING! DO NOT CHECK IN YOUR CREDENTIALS INTO PUBLIC SOURCE CONTROL
    private static final String pass = "MY_WONDERFUL_PASSWORD"; // WARNING! DO NOT CHECK IN YOUR CREDENTIALS INTO PUBLIC SOURCE CONTROL
    private static String res;
    private AsyncResponse delegate = null;

    void setDelegate(AsyncResponse d) {
        delegate = d;
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }
    @Override
    protected String doInBackground(String... params) {
        try {
            Class.forName("com.mysql.jdbc.Driver").newInstance();
            Connection con = DriverManager.getConnection(url, user, pass);
            System.out.println("Database Connection success "  + params);

            String result = "Database Connection Successful\n";
            // Let's search by dba_name and / or aka name.
            // Limit results by to top 10 results, and let user scroll

            PreparedStatement ps = con.prepareStatement("SELECT * FROM health_reports" +
                    " WHERE " +
                    "dba_name LIKE ? OR aka_name LIKE ? LIMIT 10");
            String searchPartial = params[0] + "%"; // LIKE 'Blah%'
            ps.setString(1, searchPartial);
            ps.setString(2, searchPartial);

            ResultSet rs = ps.executeQuery();
            ResultSetMetaData rsmd = rs.getMetaData();

            String sep = " | ";

            while (rs.next()) {
                result += rs.getInt(1) + sep + // id
                        rs.getInt(2) + sep + // inspection_id
                        rs.getString(3) + sep + // dba_name
                        rs.getString(4) + sep + // aka_name
                        rs.getInt(5) + sep + // license_num
                        rs.getString(6) + sep + // facility_type
                        rs.getString(7) + sep + // risk
                        rs.getString(8) + sep + // address
                        rs.getString(9) + sep + // city
                        rs.getString(10) + sep + // state
                        rs.getString(11) + sep; // zip
                try {
                    result += rs.getString(12).toString() + sep; // inspection_date
                } catch (Exception e) {
                    // e.printStackTrace();
                }
                result += rs.getString(13) + sep + // inspection_type
                        rs.getString(14) + sep + // results
                        rs.getString(15) + sep + // violations
                        rs.getString(16) + sep // location
                        ;
                result += System.lineSeparator();
                result += "------------";
                result += System.lineSeparator();
            }
            res = result;
            if (res.length() > 502) {
                Log.d("Task:", "Database Result success " + result.substring(0, 500));
            } else {
                Log.d("Task:", "Database Result success " + result);
            }
        } catch (Exception e) {
            e.printStackTrace();
            res = e.toString();
        }
        return res;
    }
    @Override
    protected void onPostExecute(String result) {
        Log.d("Task:", "onPostExecute");
        this.res = result;
        delegate.processFinish(result);
    }
}

This class uses the MySQL JDBC driver. You have to add the MySQL database connector as a module to your project. The instructions to do that are in StackOverflow – click that link and follow the instructions, which were pretty easy, at least with Android Studio 3.5.

For test purposes, I ran this demo in the Android Emulator. I typed in ‘Italian’ for the search term, and got back a bunch of results. It took a short while, because I never added any indexes to my database table, but that’s something to fine-tune later.

As a finishing touch, I built the Android APK, and loaded it onto my phone. Here’s a screenshot of the result:

Now, as mentioned earlier, you shouldn’t use a direct connection to the database in production code. A hacker might crack open your app, find the user name and password to your database, and do bad things! Ideally, you’ll want to connect to your database using some middleware which fields requests to the database, and makes sure that things like access permissions are enforced. That’s why this little Android app is just for demonstration purposes. The good part is that it can be built quickly, so you don’t have to waste time building middleware until you’re 100% sure you’re going to need it in a publicly available app!

If you found this interesting, click the subscribe button below! I write a new post about once a week.

How to access an AWS RDS using JDBC in your Android app – Part I

You’ve got a huge spreadsheet that has a lot of data in it, and you’ve built an Android app which works like a search engine on the data. Nice! But there’s a problem: when you build your app with all of that data in it, the APK is huge! You want to reduce the size of the app. And you also want to offload the search functionality onto a relational database, which is probably going to provide a more efficient search. How do you start?

This blog post explores one way to do it. It’s “quick and dirty”, and it’s not recommended to do things exactly this way. I’ll talk about why in Part II. But this method will give you a start.

Here’s a quick sketch of the idea: You put your data in the cloud using the Amazon Relational Database Service (RDS). Then you add JDBC calls to your app to access the cloud. It’s pretty quick. Here are the steps, using a simple example that I tried for myself.

Technical Details: My development environment runs Ubuntu 16.04, and I have a MySQL client and the MySQL database already installed on my local machine. I use Android Studio 3.5 IDE for building Android apps. Also, I have an Amazon AWS account set up already. You can follow this tutorial if you don’t have any of that, but then specific steps will differ for you.

Get Your Data Source Ready

For my data source, I downloaded some food inspection data from healthdata.gov in a csv (“comma-separated values”) format. I opened the csv file in a spreadsheet, selected some of the columns that I wanted, and exported them to another file, also in csv format. You can use the csv file that I generated by starting with this small, truncated version of the data. Later, you can use or create your own, very large data source for experiments.

Create an Amazon RDS MySQL Database

Visit the Amazon MySQL RDS page and click “Get Started”. If you don’t have an AWS account, you will need to sign up for one, first. Check out the pricing, if you are worried. There’s a free tier, great!

If you’re already signed in, another way to get started is to visit the AWS Management Console, search for “RDS”, and click the result for “Managed Relational Database Service”.

At this point, you’ll see a “Create Database” button. Choose “MySQL”, and click the “free tier”. Type in healthdata-1 for the name. Choose a username when requested. I’m using fullstackdev. Pick a secure password. The other parts of the form are straightforward. You can think about using IAM based authentication later. For this proof-of-concept piece of work, let’s keep it simple, and use password based authentication. For the rest, accept all defaults.

At this point, a page opens which says the database is being created.

AWS RDS creating database

Click the “modify” button. You’ll see that you can modify various things about the database later, if you want. Just be aware of this. For right now, you’ll need to “modify” the RDS so that it can be accessed from external sources – so choose “Public accessibility” and set it to Yes, and make sure to click the “Continue” button at the bottom of the page to save your changes. You need to do this so that you can create a database, load data into it, and access it via JDBC.

Now we’ve got an RDS in the cloud, and it’s accessible from our home environment. Next, we need to create a database.

Create Your Database and Manage Access

If you click the DB identifier in your RDS console, you will see an area called “Connectivity & security”. That area tells you what your endpoint is, and what your port is. The port defaults to 3306. Your endpoint will be something like healthdata-1.c84gpzpanfrn.us-east-1.rds.amazonaws.com. This is a URL you can use to access the database from another machine.

In the ‘Security’ pane, at the right, you will see your VPC (Virtual Private Cloud) security groups with a link to the default. Click that. It will take you to your Security Groups area. The default VPC security group should be preselected. Look at the bottom panel, where you should see the “Description”, “Inbound”, “Outbound”, and “Tags” tabs. Click “Inbound” and hit the “Edit” button. Click the “Add Rule” button, select MySQL/Aurora, make sure that the protocol is set to TCP/IP and the port to 3306, thne choose “MyIP” as the source. Your IP address will be set when doing this. Then hit the “Save” button.

Remember that you’ve added this rule just for your own IP address! You’re doing this for test purposes. Later, if you want, you can make different inbound rules, but this setup is good for a proof-of-concept.

Now the RDS is accessible. I am comfortable using the command line for MySQL client, so I used this to step into the cloud, and create my database. You can use whatever tool you want to do this.

First, I connected via this command:

mysql -u fullstackdev -P 3306 -p -h healthdata-1.c84gpzpanfrn.us-east-1.rds.amazonaws.com healthdata-1

The -p option tells the client to ask for a password interactively. I gave the password that I had set up earlier, and immediately, I was connected. This is what I saw:

Type: MySQL/Aurora,
Protocol: TCP
Port Range: 3306
Source: MyIP
Description: MySQL client

show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| innodb             |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.03 sec)

It’s the usual default MySQL database setup.

I had already designed a database around the food inspection data that I had decided to import. I created my own database like this:

CREATE DATABASE food_inspections;
USE food_inspections;
DROP TABLE health_reports;
CREATE TABLE health_reports (
    id INT AUTO_INCREMENT PRIMARY KEY,
	inspection_id INT,
	dba_name TEXT,
	aka_name TEXT,
	license_num INT,
	facility_type TEXT,
	risk TEXT, address TEXT,
	city TEXT, state TEXT,
	zip TEXT, inspection_date DATE,
	inspection_type TEXT, results TEXT,
	violations TEXT, location TEXT
);

I didn’t add any indexes for the columns other than the primary key. That can all be added later, when performance tuning.

Push Your Data to Amazon RDS MySQL Database

AWS provides instructions for pushing data to a MySQL RDS in the cloud. Since we have a new RDS which is already set up, we can skip straight to step 5, “Load the Data”.

They tell you to use the mysqlimport command, and you can do that if you want. There are other tools that can be used to import data, too. However, since I was already in the MySQL client, I used the LOAD DATA command, like so:

LOAD DATA LOCAL INFILE 'Food_Inspections_small.csv' INTO TABLE health_reports
	FIELDS TERMINATED BY ',' ENCLOSED BY '"'
    LINES TERMINATED BY '\n' (@inspection_id, @dba_name, @aka_name,
		@license_num, @facility_type, @risk, @address, @city, @state, @zip,
		@inspection_date, @inspection_type, @results, @violations, @location)
	SET inspection_id = @inspection_id, dba_name = @dba_name, aka_name = @aka_name,
		license_num = @license_num, facility_type = @facility_type, risk = @risk,
		address = @address, city = @city, state = @state, zip = @zip,
		inspection_date = @inspection_date, inspection_type = @inspection_type,
		results = @results, violations = @violations, location = @location;

Keep in mind that you may need to modify this command for your own purposes. I had launched the MySQL client from within the same directory where my Food_Inspections_small.csv was located, , so this command worked for me straightaway.

Now, my RDS is all set up, complete with data! That is half the battle. In my next blog post, I’ll cover how to access the RDS using an Android app.

If you found this interesting, click the subscribe button below! I write a new post about once a week.

How does S3 generate the URL with putObject method?

Recently, I noticed a question on a forum about the AWS SDK S3Client class.

The person was using the putObject method of S3Client to upload a file to an Amazon S3 bucket.

After that, he needed to figure out the URL which could be used to access that file. He had figured out that uploading a file called cat.gif could be accessed with the URL “https://s3.eu-west-3.amazonaws.com/aws.mybucket.es/mysite/httpdocs/cat.gif”.

The problem was that when he uploaded a file whose name included special characters, such as an accented o – “ó” – he couldn’t figure out a consistent way to construct the URL. A character with an accent got URL encoded, but the parenthesis character in a file name did not!

He was trying to figure out the implementation details for the putObject method, and couldn’t find any documentation about it.

The answer to his question was that he was asking the wrong question! There’s a software principle that you should “write code to the interface, not to the implementation“.

As consumers of the S3Client API, we should not be trying to figure out the URL to an uploaded file. Rather, we should be asking the interface for the URL. If AWS revealed the details of their URL construction scheme, it would be very painful if they ever decided to change it, both for them and for users of S3. Further, programmers everywhere would be forced to implement the algorithm that AWS declared for URL construction in all the different languages that are supported by the AWS SDK. That’s a lot of duplicated effort.

Fortunately, AWS gives us an interface that can be used to obtain the URL after a file is uploaded. The result of S3Client->putObject contains an ObjectURL property. We can use that to get the URL, which we can record however we want for later use. Here’s an example:

...
$result = $s3->putObject(...);
$url = $result['ObjectURL'];
...

The full source code for this example of using the S3Client putObject method is at github.

So you see that there’s no need to figure out how AWS implements the URL for our file. AWS gives us the URL immediately when our file is uploaded.

If you found this interesting, click the subscribe button below! I write a new post about once a week.

How to use Amazon AWS Translate with PHP 7.0

Amazon AWS Translate is a pretty cool translation service. You can get started free of charge. Let’s give it a try. This demo assumes you’ve got an AWS account (if not, first go get that). I’m using PHP 7.0 on an Ubuntu 16.04 box.

First, create a new IAM (Identity and Access Management) group. Let’s call it TranslateGroup. Give it TranslateReadOnly permissions. Don’t know how to do this? Sign into your AWS console, and search for “IAM”. That will take you to the right place for dealing with IAM.

Add a new user to this group. Let’s call this user TranslateUser. Give it programmatic access only.

When you see your Access key ID and secret, copy them into your AWS credentials file (in Linux, this is located under ~/.aws/credentials). Set the header for the profile to be [TranslateUser].

Now that you’ve created a user, make sure you’ve installed the AWS PHP SDK. I did this in my demo directory, just by downloading the SDK and unzipping it. The contents of my directory are pretty simple:

~/TranslateDemo$ ls -lairt
total 164
18226436 drwxr-xr-x   3 fullstackdev fullstackdev     4096 Jul 11 15:06 Psr
18226304 drwxr-xr-x   2 fullstackdev fullstackdev     4096 Jul 11 15:06 JmesPath
18226324 drwxr-xr-x   7 fullstackdev fullstackdev     4096 Jul 11 15:06 GuzzleHttp
18226301 -rw-r--r--   1 fullstackdev fullstackdev   129259 Jul 11 15:06 aws-autoloader.php
18226446 drwxr-xr-x 197 fullstackdev fullstackdev    12288 Jul 11 15:06 Aws
   6961244 -rw-rw-r-- 1 fullstackdev fullstackdev      958 Sep 16 20:32 test_translate.php
...

It’s quick and easy to code up the rest. Here’s some demo code (test_translate.php):

<?php
require './aws-autoloader.php';

use Aws\Translate\TranslateClient;
use Aws\Exception\AwsException;

$client = new Aws\Translate\TranslateClient([
    'profile' => 'TranslateUser',
    'region' => 'us-west-2',
    'version' => 'latest'
]);

// Translate from English (en) to Spanish (es).
$currentLanguage = 'en';
$targetLanguage= 'es';
$textToTranslate = "Call me Ishmael. Some years ago—never mind how long precisely—having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world.";

echo "Calling translateText function on '".$textToTranslate."'\n";

try {
    $result = $client->translateText([
        'SourceLanguageCode' => $currentLanguage,
        'TargetLanguageCode' => $targetLanguage,
        'Text' => $textToTranslate,
    ]);
    echo $result['TranslatedText']."\n";
} catch(AwsException $e) {
    // output error message if fails
    echo "Failed: ".$e->getMessage()."\n";
}

Run this from the command line: php test_translate.php. The output is:

Calling translateText function on 'Call me Ishmael. Some years ago—never mind how long precisely—having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world.'
Llámame Ishmael. Hace algunos años, no importa cuánto tiempo precisamente— teniendo poco o ningún dinero en mi bolso, y nada particular que me interesara en la costa, pensé que navegaría un poco y vería la parte acuosa del mundo.

Pretty easy, right?

If you found this interesting, click the subscribe button below! I write a new post about once a week.

How to use the Amazon AWS SDK for Textract with PHP 7.0

The Amazon AWS Textract API lets you do OCR (optical character recognition) on digital files. It’s actually pretty easy to use, although there’s some prep work.

This post has instructions for using the Textract API with their PHP SDK. I’m using PHP version 7.0 on an Ubuntu 16.2 operating system. This demo works as of September 2019.

Step 1: Create the project

Create a folder for your project, for example:

mkdir ~/TextractDemo ; cd ~/TextractDemo

Instructions for getting started with the SDK for PHP are here. First, download the .zip file as described on that page. Then, extract the zip file to the root of your project. That adds a lot of files and folders to the project root. For example, the “Aws” folder is added. This is what you should see when listing the contents of this directory:

~/TextractDemo$ ls -lairt
total 676
  396747 -rw-r--r--   1 fullstackdev fullstackdev  10129 Sep 12 14:11 README.md
  531373 drwxr-xr-x   3 fullstackdev fullstackdev   4096 Sep 12 14:11 Psr
  396739 -rw-r--r--   1 fullstackdev fullstackdev   2881 Sep 12 14:11 NOTICE.md
  399132 -rw-r--r--   1 fullstackdev fullstackdev   9202 Sep 12 14:11 LICENSE.md
  926072 drwxr-xr-x   2 fullstackdev fullstackdev   4096 Sep 12 14:11 JmesPath
  396755 drwxr-xr-x   7 fullstackdev fullstackdev   4096 Sep 12 14:11 GuzzleHttp
  399129 -rw-r--r--   1 fullstackdev fullstackdev 478403 Sep 12 14:11 CHANGELOG.md
  396748 -rw-r--r--   1 fullstackdev fullstackdev 132879 Sep 12 14:11 aws-autoloader.php
  531270 drwxr-xr-x 203 fullstackdev fullstackdev  12288 Sep 12 14:11 Aws
  396729 drwxr-xr-x   6 fullstackdev fullstackdev   4096 Sep 15 09:48 .
13500418 drwxr-xr-x  46 fullstackdev fullstackdev  20480 Sep 15 09:49 ..

Step 2: Create an IAM User

In order to use the Textract API, you need an Amazon AWS account. So if you don’t have that already, go follow the instructions to do that now.

Assuming you’ve got an AWS account, next, you need to create an IAM (Identity and Access Management) user. If you are signed in to your AWS console, just search for “Identity and Access Management”, and it takes you to the right place to create an IAM user. There’s an area called “Create individual IAM users”. Go there, click the “Manage Users” button, click the “Add User” button, choose a name like TextractUser, and give this user programmatic access only. Once you’ve created the name, go to the next step, where you can add the user to a specific group. Create a group which has the AmazonTextractFullAccess policy name. Name it something like TextractFullAccessGroup, and save that. Add the user you just created to this group. The next step lets you add tags to the user, but you can leave that blank.

In the Review (last) step, you are given the user’s access key ID and secret key (which is hidden – you will have to reveal it to copy it). Save these in a secure place! As the documentation says, “This is the last time these credentials will be available to download. However, you can create new credentials at any time.” (So if you lose them somehow, you can always generate a new set.)

The credentials that you just created may be saved in the file ~/.aws/credentials on Linux systems. Here’s a quick rundown about that file.

If this file already exists, you can add to it. Here’s the documentation for adding lines to an AWS credentials file. On that page, it gives you an example credentials file with this content:

[default]
aws_access_key_id=AKIAIOSFODNN7EXAMPLE
aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

[user1]
aws_access_key_id=AKIAI44QH8DHBEXAMPLE
aws_secret_access_key=je7MtGbClwBF/2Zp9Utk/h3yCo8nvbEXAMPLEKEY


Instead of user1, add the line [TextractUser] (or whatever user name you used in the “creating user” step above). Copy and paste your access key id and secret key as shown.

The credentials file is normally created when installing the AWS CLI. So if you do not already have a credentials file, install the CLI first. Then you can add users to the file.

Now we’re ready to use Textract. Let’s try to detect text in a sample “document” – the image file shown below. If you are following along, you can right click and save this image, or you can try it on one of your own image files (i.e. one that contains text!).

Test file for Textract

Call Textract using the SDK

You can have Textract analyze images that are in an S3 bucket. However, for demo purposes, that is overkill! It is simpler and quicker to read in an image file as bytes, and send that to Textract for analysis. That’s what we will do.

The source code only needs to do three things. First, it needs to create a Textract client. Second, it needs to read in the image file as bytes. Third, the client needs to call the Textract API. Here’s the demo code:

<?php
/*
 * To run this project, make sure that the AWS PHP SDK has been unzipped in the current directory.
 * 
 * Caution: this is not production quality code. There are no tests, and there is no error handling.
 */
require './aws-autoloader.php';

use Aws\Credentials\CredentialProvider;
use Aws\Textract\TextractClient;

// If you use CredentialProvider, it will use credentials in your .aws/credentials file.
/*
$provider = CredentialProvider::env();
$client = new TextractClient([
	'profile' => 'TextractUser',
    'region' => 'us-west-2',
	'version' => '2018-06-27',
	'credentials' => $provider
]);
*/
$client = new TextractClient([
    'region' => 'us-west-2',
	'version' => '2018-06-27',
	'credentials' => [
        'key'    => 'AKIAI44QH8DHBEXAMPLE',
        'secret' => 'je7MtGbClwBF/2Zp9Utk/h3yCo8nvbEXAMPLEKEY'
	]
]);

// The file in this project.
$filename = "aws_cli_text_document.jpg";
$file = fopen($filename, "rb");
$contents = fread($file, filesize($filename));
fclose($file);
$options = [
    'Document' => [
		'Bytes' => $contents
    ],
    'FeatureTypes' => ['FORMS'], // REQUIRED
];
$result = $client->analyzeDocument($options);
// If debugging:
// echo print_r($result, true);
$blocks = $result['Blocks'];
// Loop through all the blocks:
foreach ($blocks as $key => $value) {
	if (isset($value['BlockType']) && $value['BlockType']) {
		$blockType = $value['BlockType'];
		if (isset($value['Text']) && $value['Text']) {
			$text = $value['Text'];
			if ($blockType == 'WORD') {
				echo "Word: ". print_r($text, true) . "\n";
			} else if ($blockType == 'LINE') {
				echo "Line: ". print_r($text, true) . "\n";
			}
		}
	}
}
?>

You’ll need to edit this source code to use your own AWS credentials. Once you do that, you should be able to run the code and view the output, as shown here:

php textract_demo.php 
Line: The AWS CLI is updated frequently with support for new services and commands.
Word: The
Word: AWS
Word: CLI
...

That’s it! Feel free to email me with any questions: fullstackdev@fullstackoasis.com.

References:

[1] Stackoverflow question about AWS Credentials

If you found this interesting, click the subscribe button below! I write a new post about once a week.