PHP Apis
PHP Apis
APIs
Doorways to Your Apps
Features
4 Discovering API Platform 30 Writing Better CLI Tools with
Kévin Dunglas Symfony Components
Juan Manuel Torres
18 Build APIs on Existing
Web Applications with Apigility 42 Raspberry Pi with PHP on Top
Michelangelo van Dam Dylan Intorf
Columns
3 Editorial: 60 Security Corner:
Doorways to Your Apps Passwords are Dead,
Oscar Merida Long Live Passwords!
Chris Cornutt
48 Education Station:
Date and Time Handling 63 Community Corner:
with Carbon Interview with Jeffrey Carouth
Matthew Setter Joe Devon
55
Leveling Up: 66 MONTH IN REVIEW:
Finding the Solution to the DECEMBER HAPPENINGS
Problem
David Stockton 67 finally{}:
Different Paths for the P’s in LAMP
Eli White
CODE PACKAGE
musketeers.me, LLC is the parent company and owner of the php[architect] brand.
API Platform includes all you need • Set CORS headers easily.
to build a high quality (HATEOAS • Set HTTP cache headers to improve performance.
https://en.wikipedia.org/wiki/HATEOAS) API: • Specify and test your API using Behat contexts
dedicated to APIs.
• Bootstrap a data model reusing Schema.org
vocabularies, including Doctrine ORM mapping, It looks daunting but you will be amazed to see how
Symfony validation constraints, and PHPDoc. easy it is.
• Expose—in a few seconds—a fully working
level 3 REST API with a ton of features: CRUD, API Platform is developed by Les-Tilleuls.coop, a
pagination for collections, server-side validation, French worker cooperative providing professional
relation support, filtering, sorting, JSON-LD, and services around web applications and e-commerce.
Hydra Core Vocabulary support. Full disclosure: I’m the creator of API Platform and the
• Get an automatically generated Swagger-like API CEO of this company.
doc that includes a sandbox.
• Easily add stateless JSON Web Token or OAuth 2
authentication.
Requirements:
• PHP: 5.5+
• API Platform—https://api-platform.com
• Composer—https://getcomposer.org
• OpenSSL—https://www.openssl.org
Other Software:
• A MySQL or a PostgreSQL DBMS
• Postman (optional)—https://www.getpostman.com
Related URLs:
• Creating Custom Data Providers—http://phpa.me/api-platform-custom-providers
• EasyRdf library—http://www.easyrdf.org
• Guzzle—https://github.com/guzzle/guzzle
• DBPedia—http://dbpedia.org
• FosHttpCache—https://foshttpcache.readthedocs.org
• Hydra Console—https://github.com/lanthaler/HydraConsole
• Hydra Core—https://github.com/bergos/hydra-core
• Hydra Core Vocabulary—http://hydra-cg.com
• IRI—https://en.wikipedia.org/wiki/Internationalized_resource_identifier
• JSON-LD—http://json-ld.org
• JSON-LD context http://www.w3.org/TR/json-ld/#the-context
• JSON Web Token—http://jwt.io
• Les-Tilleuls.coop—https://les-tilleuls.coop
• LexikJWTAuthenticationBundle http://phpa.me/LexikJWTAuth
• Linked Open Vocabularies—http://lov.okfn.org
• MySQL—http://www.mysql.com
• NelmioCorsBundle—https://github.com/nelmio/NelmioCorsBundle
• OAuth 2—http://oauth.net
• PostgreSQL—http://www.postgresql.org
• Running API Platform in Docker–http://phpa.me/run-api-platform-docker
• Schema.org—http://schema.org
• SPARQL—https://en.wikipedia.org/wiki/SPARQL
• Symfony Best Practices—http://phpa.me/symfony-best-practices
• Writing Behat Features—http://phpa.me/writing-features-behat
The philosophy advocated by the framework is to first Another strength of the API-first approach is that
build an API enclosing the whole business logic of the any partner, customer, or provider with access to the
application(s) (API-first). This API will be the unique API can leverage the exact same set of features as the
entry-point for accessing and modifying data. official website or app. This approach can be summed
up by the motto write your business logic once, use it
The presentation logic (including forms) is stored in from any client.
client applications. Clients can be modern HTML5/
Angular/React/whatever responsive web apps It enforces by design low coupling, separation of
querying the API trough AJAX (the official API Platform
concerns, and high application interoperability. It helps avoid the typical business logic duplication that occurs
during the application growth: features are added (first a website, then a mobile site, then an API for third-party
apps…) and, of course, all the copy/paste/adapt that occurs in each application component so the maintenance
becomes a nightmare.
The installer asks some configuration parameters, starting with the database driver to use and DB credentials.
By default, API Platform comes with the Doctrine ORM, but it can be integrated with any other persistence
library. Doctrine supports MySQL, Oracle, Microsoft SQL Server, SAP Sybase, SQLite, and Drizzle, but I strongly
recommend using MySQL or Postgres for this tutorial and for your APIs in general: those DBMS have the best
support by Doctrine, and a lot of community libraries and bundles support only them. The default prompts are
shown below. Depending on your database setup, you may need to specify database_host as localhost.
database_driver (pdo_mysql):
database_host (127.0.0.1):
database_port (null):
database_name (api):
database_user (root):
database_password (null):
Then the installer asks for SMTP credentials. This is only useful if you want to send email from your API (as with
Symfony, the Swift Mailer library is included).
mailer_transport (smtp):
mailer_host (127.0.0.1):
mailer_user (null):
mailer_password (null):
Finally, you’ll be asked for some configuration parameters specific to the API.
locale (en):
API Platform has native CORS support through NelmioCorsBundle (see related URLs). Set the URL of your default
web client and API Platform will automatically set CORS headers that allow the webapp to query the API:
cors_allow_origin ('http://localhost'):
The installer also asks you to name and describe your API. These details will be used in the Hydra documentation
of the API:
Finally enter a long (32 characters) and complex random token. It will be used by security features:
secret (ThisTokenIsNotSoSecretChangeIt):
Once the installation is complete, go to the new the default AppBundle. It will enable some
my-directory-api directory that appears. It magic: for instance, your Doctrine entities will
contains the skeleton of your new API. All required be automatically detected and registered, and
dependencies (basically Symfony, the Doctrine validation constraints will apply.
ORM, and API Platform components) have been • We already described the vendor/ directory;
downloaded in the vendor/ directory. finally, the web/ directory must be the document
root of your project. It’s the only directory
The installer also created a file called that must be publicly exposed. It contains
app/config/parameters.yml containing the two front controllers (app.php for the prod
parameters it asked for. This file can be tweaked at any environment and app_dev.php for developing
time of the project life. It must not be versioned, as it and debugging) and public assets such as the
allows adapting the configuration of the current install robots.txt file.
to its environment. You can add new configuration
The root directory also contains some useful files:
parameters with default values by editing the
composer.json tracks installed PHP libraries and
app/config/parameters.yml.dist file.
their version and docker-compose.yaml allows
API Platform to be instantly run in a Docker container
In fact, this skeleton is already a working app. It
http://phpa.me/run-api-platform-docker.
contains some example code that just works. Run the
built-in PHP web server to give API Platform a try:
When you are done playing with the demo app,
php app/console server:run empty the file app/config/schema.yml and the
src/AppBundle/Entity directory. Don’t worry—
The demo API is now available at we’ll fill them again in the next section.
http://127.0.0.1:8000. I recommend using Postman
https://www.getpostman.com to query the API. It’s
an API client on steroids that will drastically increase
Generating the Data Model
your productivity when developing and debugging. Interoperability of databases at web scale—also
known as the semantic web—is an old dream of
Go to http://127.0.0.1:8000/doc to browse the internet scientific and technical communities. Thanks
beautiful auto-generated API documentation and the to the joint efforts of search engine giants, universities,
sandbox. and the e-commerce industry (under the W3C), this
dream is coming true: its name is the Linked Data
Take a look at the content of the project directory. If movement.
you are familiar with Symfony, you will recognize the
directory structure of the well-known standard edition. Imagine querying in a single request most public
This is not a coincidence, as the API Platform edition databases on the Internet. Imagine a search engine
you just installed is a perfectly valid Symfony crawler able to easily index and find all publications of
app following Symfony’s best practices. If you are a given author across all public websites, or imagine
new to Symfony, you deserve some more detailed a price comparator extracting with ease products and
explanation: offers data from e-commerce websites or, more in sync
with our small project, imagine a CRM able to import
• The app/ directory contains configuration files
customer data from any source.
and assets (non PHP-code) for the project. It also
contains logs and a cache (inside directories of
To enable such possibilities, websites and apps
the same name).
must expose their data in a standard format (RDF/
• The bin/ directory stores command line tools
JSON-LD) and embrace a common vocabulary. If such
provided with API Platform. You’ll learn how to
prerequisites are met, it becomes possible to run
use the schema generator in a few lines. It also
queries on multiple data sources with languages like
contains the behat command.
SPARQL (see Related URLs). Open web standards and
• The features/ directory contains specifications
API Platform help a lot in all these fields, but let’s focus
of the API written in the Gherkin language (see
on the common vocabulary for now.
Related URLs) that will be executed by Behat; it
will not be covered in this article.
Schema.org is a vocabulary defining a lot of
• The src/ directory is the most important one, as
things frequently exposed on the web, including
it contains all the source code of your application.
publications, people, organizations, events, product,
For a typical app, your code will be stored in
offers, and reviews, among many, many other things.
LISTING 1
These data models are well-designed and widely used,
01. types:
especially to add search engine readable structured
02. Person:
markup through microdata and to expose raw data
03. parent: false
(through APIs and large dumps such as DCPedia—see
04. properties:
Related URLs) in a common and popular vocabulary.
05. givenName: ~
06. additionalName: ~
Guess what? API Platform comes with a handy
07. familyName: ~
data model generator allowing the transformation
08. worksFor: { range: Corporation }
of Schema.org data models into a set of PHP entity
09. jobTitle: ~
classes, including:
10. gender: ~
11. address: { range: PostalAddress } • selected properties with corresponding mutator
12. telephone: ~ methods
13. faxNumber: ~ • relations
14. url: ~ • Doctrine ORM mappings (guessed from Schema.
15. description: ~ org type information)
16. internalScore: { range: Integer } • Symfony validation constraints
17. Corporation: • Full and extensive PHPDoc
18. parent: false • API Platform @Iri annotations (to use the
19. properties: Schema.org namespace during the JSON-LD
20. name: ~ serialization)
21. legalName: ~
Schema.org provides some types that will allow
22. address: { range: PostalAddress }
us to bootstrap our customer directory with the
23. email: ~
API Platform schema generator. The Schema.org
24. telephone: ~
website has a convenient list of all available types
25. faxNumber: ~
http://schema.org/docs/full.html. We will use this to
26. url: ~
bootstrap our data model generically:
27. description: ~
28. PostalAddress: • http://schema.org/Person: a person (a customer
29. parent: false for our app)
30. properties: • http://schema.org/Organization: an
31. addressCountry: { range: Text } organization (a company for our app)
32. addressLocality: ~ • http://schema.org/PostalAddress: an address
33. addressRegion: ~
Once you have chosen types applicable for your data
34. postOfficeBoxNumber: ~
model, report them in the app/config/schema.yml
35. postalCode: ~
file, see Listing 1.
36. streetAddress: ~
37. rdfa:
38. - https://raw.githubusercontent.com/schemaorg/schemaorg/sdo-phobos/data/schema.rdfa
39. namespaces:
40. entity: AppBundle\Entity
41. annotationGenerators:
42. - ApiPlatform\SchemaGenerator\AnnotationGenerator\PhpDocAnnotationGenerator
43. - ApiPlatform\SchemaGenerator\AnnotationGenerator\DoctrineOrmAnnotationGenerator
44. - ApiPlatform\SchemaGenerator\AnnotationGenerator\ConstraintAnnotationGenerator
45. - ApiPlatform\SchemaGenerator\AnnotationGenerator\DunglasApiAnnotationGenerator
In the types section of this file, all types you want to generate must be reported. For each type, also report the
list of properties you want (the default behavior is to generate all properties).
The generator is able to generate a class hierarchy (using abstract classes and the @MappedSuperclass
Doctrine annotation), but another solution—preferred here—is to disable the parent generation (with
parent: false) and directly include properties from the parent type in its child. Here the Thing type is ignored
(no related PHP class will be generated), but its name property will be generated directly in the Corporation
class. By doing so, we limit the number of generated classes and keep our data model simple. We also avoid the
numerous problems that can occur when dealing with Doctrine mapped superclasses.
You can also see that we force the type of some properties by using the range keyword. This is necessary when
a type has several types: it’s perfectly valid from a RDF point of view, but can lead to problems and add extra
complexity in a PHP class model. The generator does not allow a property to have several types (by default, the
first one defined will be the one that’s picked). This feature is also useful when generating custom properties: the
internalScore property of the Person class is not part of Schema.org, but the generator allows you to define
custom fields. In such cases, specifying a type is mandatory.
Note: the development version of the generator also allows you to specify in the configuration file whether a
property can be null or not, whether it must be unique, and its Symfony Serializer @Groups annotation.
The rdfa section allows you to specify which RDFa files to extract the schema from. If omitted, it uses the Schema.
org production file but—at time of writing—this file is buggy, so the developement version is used here.
The development version of the schema generator contains another interesting feature: it can generate a data
model from any vocab defined in RDF, including the bunch of open domain vocabs referenced in Linked Open
Vocabularies (see Related URLs). It supports RDFa, JSON-LD, RDF/XML, RDF/JSON, Turtle, and N-Triples formats,
thanks to the underlying EasyRdf library (see Related URLs). It opens new horizons. For example, define your
whole data model in RDF, using Schema.org as the foundation and specialized with custom extensions, then
generate related PHP classes with the API Platform schema generator.
The namespace section indicates the PHP namespace for generated classes. Here we keep with Symfony best
practices but, as the generator is a standalone component, you can use it to generate PHP classes for any project,
using Symfony, another framework, or no framework at all.
It’s time to generate the PHP classes. Run the following command:
A working set of PHP classes will appear in the src/AppBundle/Entity directory. Open some files: you’ll
see that they respect PSR specifications and contain properties, getters, setters, exhaustive PHPDoc, Doctrine
mapping, annotation constraints, and API bundle metadata! API Platform saved us from a lot of tedious work.
app/console doctrine:database:create
app/console doctrine:schema:create
Keep in mind that this schema generator is only intended to bootstrap the data model. Then you will need to
write your domain specific types and properties, add more validation constraints, and tweak Doctrine mapping.
Once files are generated and you start to edit them, remove api-platform/schema-generator from your
composer.json file. It is not required at runtime.
To expose your entities through your API, you need to define a Resource service by entity class. Replace the
content of app/config/services.yml with the lines in Listing 2.
{
"name": "Les-Tilleuls.coop",
"description": "Professional services",
"email": "contact/les-tilleuls.coop",
"url": "http://les-tilleuls.coop"
}
I’m sure you spotted the error: the email is invalid. The API bundle automatically validates submitted data using
the Symfony Validator Component. Here the schema generator was smart enough to add an email validation
constraint. The API leverages them and returns the following:
{
"@context": "/contexts/ConstraintViolationList",
"@type": "ConstraintViolationList",
"hydra:title": "An error occurred",
"hydra:description":
"email: This value is not a valid email address.\n",
"violations": [
{
"propertyPath": "email",
"message": "This value is not a valid email address."
}
]
}
As you can see, the error is descriptive enough to be rendered client-side. As with all data returned by the API, it
is a valid JSON-LD document. It follows the error serialization format defined in the Hydra Core Vocabulary spec.
Correct the email address and send the request again. you’ll see the output in Listing 3.
The output you see is:
The API bundle automatically paginates collections
{ and serializes them accordingly to Hydra’s Paged
"@context": "/contexts/Corporation", Collection specification. Navigate in the collection
"@id": "/corporations/1", using metadata present starting with the hydra prefix.
"@type": "http://schema.org/Corporation",
"address": null, Last but not least, API Platform automatically
"description": "Professional services", generates machine-readable documentation of all
"email": "[email protected]", exposed services. This description follows the Hydra
"faxNumber": null, standard.
"legalName": null,
"name": "Les-Tilleuls.coop", It enables the API to be auto-discovered by software
"telephone": null, agents aware of the Hydra specification. Give Hydra
"url": "http://les-tilleuls.coop" console a try (see Related URLs): install it and enter
} your API URL, and you’ll browse a 100 percent
dynamic, auto-generated administration interface
This time the new item was correctly saved in the for your API. If you want to play with the auto-
database. Again, the returned document is in JSON- discoverability of the API in a Javascript program, try
LD. It points to an automatically generated JSON-LD out hydra-core (see Related URLs), a library able to
context through the @context key. API Platform parse the exposed Hydra documentation.
automatically assigned an IRI to the document, based
on the database identifier: /corporations/1. This
identifier is unique across the Internet and enables Embedding Resources
this particular corporation to be identified in any other
When you play with your API, you’ll see that
system (internal or external). The API bundle also
retrieving a person (or a corporation) and the
leveraged @Iri annotations added by the schema
associated address requires two HTTP requests:
generator (or manually) to generate a @type key in the
they are separated collections. This is bad from a
JSON document referencing a Schema.org type.
performance point of view. But don’t panic: the API
bundle supports embedding relations (in read and
Thanks to the @type key, a software
write modes). To do so, you need to
supporting RDF, such as a triple store, will
know the type of the document LISTING 3
(http://schema.org/Corporation). 01. {
Thanks to the mapping defined in the 02. "@context": "/contexts/Corporation",
JSON-LD context, it will know—for 03. "@id": "/corporations",
instance—that the type of the email 04. "@type": "hydra:PagedCollection",
property is https://schema.org/email. 05. "hydra:totalItems": 1,
Such information allows SPARQL queries 06. "hydra:itemsPerPage": 30,
to be run against data coming from 07. "hydra:firstPage": "/corporations",
different databases using the Schema.org 08. "hydra:lastPage": "/corporations",
vocabulary. 09. "hydra:member": [
10. {
Thanks to API Plaform, you got all this 11. "@id": "/corporations/1",
next-generation semantic stuff for free, 12. "@type": "http://schema.org/Corporation",
with ease and powered only by open and 13. "address": null,
standard web technologies: raw JSON 14. "description": "ProfesSional services",
(all JSON-LD documents are valid JSON 15. "email": "[email protected]",
parseable with any language), HTTP, PHP, 16. "faxNumber": null,
MySQL, no Java ;)! 17. "legalName": null,
18. "name": "Les-Tilleuls.coop",
The API bundle supports POST, 19. "telephone": null,
PUT, DELETE, GET on items 20. "url": "http://les-tilleuls.coop"
and, more interestingly, GET 21. }
queries on collections. Go to 22. ]
http://127.0.0.1:8000/corporations— 23. }
use Symfony serialization groups. Open the Person entity class and add the following use statement:
use Symfony\Component\Serializer\Annotation\Groups;
Do the same for the PostalAddress class, then add the following annotation to all properties of Person you
want to expose (including the address property) and all properties of the PostalAddress entity class you want
to be embedded in the Person JSON-LD document:
/**
* ...
* @Groups({"person_read"})
*/
private $address;
I’ve chosen to add the @Groups annotation to all properties of Person and PostalAddress except for $id (it is
automatically handled by the IRI system).
The last step is to indicate in the service definition which groups to use for the serialization (and optionally for the
deserialization). In app/config/services.yml, update the resource.person definition as shown:
resource.person:
parent: "api.resource"
arguments: [ "AppBundle\\Entity\\Person" ]
calls:
- method: "initNormalizationContext"
arguments: [ { groups: [ "person_read" ] } ]
tags: [ { name: "api.resource" } ]
Add an address by issuing a POST request to /postal_addresses:
{
"addressCountry": "FR",
"addressLocality": "Lomme",
"postalCode": "59160",
"streetAddress":
"Euratechnologies - bâtiment Canal, 2 rue Hegel"
}
Grow Professionally Join us for the next meeting – start your habit of continuous learning.
Stay Connected
Check out our upcoming meetings at nomadphp.com
Or follow us on Twitter @nomadphp
Then add a person with this address and working for the corporation we added earlier with a POST to /people:
{
"givenName": "Kévin",
"familyName": "Dunglas",
"address": "/postal_addresses/1",
"worksFor": "/corporations/1",
"email": "[email protected]",
"internalScore": 5
}
"@context": "/contexts/Person",
"@id": "/people/1",
"@type": "http://schema.org/Person",
"additionalName": null,
"address": {
"@id": "/postal_addresses/1",
"@type": "http://schema.org/PostalAddress",
"addressCountry": "FR",
"addressLocality": "Lomme",
"addressRegion": null,
"postalCode": "59160",
"postOfficeBoxNumber": null,
"streetAddress": "Euratechnologies - bâtiment Canal, 2 rue Hegel"
},
"description": null,
"familyName": "Dunglas",
"gender": null,
"givenName": "Kévin",
"jobTitle": null,
"telephone": null,
"url": null,
"worksFor": "/corporations/1",
"internalScore": 5
}
You retrieved the person document with its associated address in one request!
If you inspect HTTP headers sent by the API, you’ll now see a Cache-Control and an E-tag header. If your client
honors such headers, data will be stored in cache for 1 minute.
This bundle is not shipped directly with API Platform; you need to install it through Composer:
Basically, the concept behind JWT is to send a signed token to the client. Then the client must send this token at
each request and the server will verify its validity. To do so, the bundle requires public and private OpenSSL keys.
Generate them:
mkdir -p app/var/jwt
openssl genrsa -out app/var/jwt/private.pem -aes256 4096
openssl rsa -pubout -in app/var/jwt/private.pem -out app/var/jwt/public.pem
lexik_jwt_authentication:
private_key_path: %kernel.root_dir%/var/jwt/private.pem
public_key_path: %kernel.root_dir%/var/jwt/public.pem
pass_phrase: %jwt_key_pass_phrase%
token_ttl: 86400
To not hard code the secret key, we create a new parameter. Fill the jwt_key_pass_phrase with the pass
phrase chosen during the OpenSSL key generation. Do it only in app/config/parameters.yml and never in
app/config/parameters.yml.dist, as this secret phrase must not be versioned.
parameters:
#...
jwt_key_pass_phrase: MySecretPassPhrase
api_login:
path: /login
Finally, the Symfony firewall must be configured. Our users will be stored in a config file. There are two types of
users, a regular and an admin. The regular user can read data but has no modification credential. The admin can
do everything. Anonymous users are not allowed to access the API at all.
In the providers section we configure the Symfony Security Component to use the list of users defined in our
parameters file. Passwords must be hashed with the bcrypt algorithm (encoders section). Add users in your
app/config/parameters.yml like this:
parameters:
# ...
users:
olivier:
password: '$2y$12$uTd0HB/cfygjzVEQm28FRuirIaRctp7uUBnvOdQFsl0/MhmPqwBi.'
roles: 'ROLE_USER'
kevin:
password: '$2y$12$uTd0HB/cfygjzVEQm28FRuirIaRctp7uUBnvOdQFsl0/MhmPqwBi.'
roles: 'ROLE_ADMIN''
Here we defined a regular user (role ROLE_USER) with olivier as username and an admin named kevin. The
password must be encoded using the app/console security:encode-password Symfony command (here it’s
test for both).
LISTING 5
Three firewalls are defined in
the firewalls section: 28. security:
29. providers:
• dev: no security at all, 30. in_memory:
used for routes available 31. memory:
only in dev environment, 32. users: %users%
such as the profiler 33.
• login: anonymous 34. encoders:
access allowed to the 35. Symfony\Component\Security\Core\User\User:
JWT endpoint allowing 36. algorithm: bcrypt
retrieval of a valid token 37. cost: 12
• api: all other URLs 38. firewalls:
require a valid JWT token 39. dev:
to be accessed 40. pattern: ^/(_(profiler|wdt|error)|css|images|js)/
41. security: false
42. login:
43. pattern: ^/login$
44. stateless: true
45. anonymous: true
46. form_login:
47. check_path: /login
48. success_handler:
49. lexik_jwt_authentication.handler.authentication_success
50. failure_handler:
51. lexik_jwt_authentication.handler.authentication_failure
52. require_previous_session: false
53. api:
54. pattern: ^/
55. stateless: true
56. lexik_jwt: ~
57. access_control:
58. - { path: ^/, roles: [ROLE_USER, ROLE_ADMIN], methods: [GET] }
59. - { path: ^/, roles: ROLE_ADMIN, methods: [POST, PUT] }
Finally, in the access_control section we define that regular users can access all URLs with the GET method,
but all other methods are available only for admins. As for cache header definition, the REST architecture allows
security and cache policies to be configured very easily, based on URL patterns and HTTP methods.
Restart the server, and try to access any endpoint of the API: you will get a 401 HTTP status code and the
following error:
{"code":401,"message":"Invalid credentials"}
To get a valid JWT token, send a POST request to http://127.0.0.1:8000/login with two parameters encoded as
form data (unlike other endpoints, the JWT endpoint takes standard form data as input—no raw JSON documents):
_username containing the username and _password containing the raw password.
{"token":"header.payload.signature"}
Use the token value (and not the whole JSON document) to access the API. Just add an Authorization HTTP
header with Bearer the.full.token as value. Keep in mind that JWT is a stateless (cookie-less) authentication
mechanism, which means that you must add this header to every issued request.
Conclusion
In this introduction you discovered that API Platform eases the development of modern and extremely powerful
REST APIs. We mostly used RAD features allowing us to bootstrap a working hypermedia API in a very short time.
But API Platform comes with more advanced features, including collection filtering and sorting, a powerful event
system, and the ability to create custom operations and controllers. It is designed in a way that allows you to hook
your custom code almost everywhere. Give it a try!
Twitter: @dunglas
2
FEATURE
DisplayInfo()
cd /path/to/workspace
curl -sS https://apigility.org/install | php Apigility Welcomes You FIGURE 3
After installation, Apigility was
launched on the built-in PHP web
server running on port 8888, so all
you had to do was point your browser
to http://localhost:8888/ to see
the nice welcome screen of Apigility.
WARNING: This is a private repository! Do not try to pull it in; use your own application for the purpose of
following along.
Avoiding Collisions
LISTING 2
Since our application was written
in Zend Framework 1 and Apigility is 01. <?php
based on Zend Framework 2, we need 02.
to make sure both applications are 03. // Define path to application directory
not colliding with each other, as both 04. defined('APPLICATION_PATH')
use APPLICATION_PATH to define the 05. || define('APPLICATION_PATH',
location of the application. 06. realpath(__DIR__ . '/application'));
07.
Simply change APPLICATION_PATH 08. // Define application environment
to APIGILITY_PATH in the 09. defined('APPLICATION_ENV')
public/index.php bootstrap file 10. || define('APPLICATION_ENV',
using sed. 11. (getenv('APPLICATION_ENV') ?
12. getenv('APPLICATION_ENV') : 'production'));
sed -i '' 's/APPLICATION_PATH/'. 13.
'APIGILITY_PATH/g' 14. // Ensure library/ is on include_path
public/index.php 15. set_include_path(implode(PATH_SEPARATOR, array(
16. realpath(APPLICATION_PATH . '/../library'),
17.
Autoloading ZF1 18. )));
get_include_path(),
A dialog window pops up where we can fill out the name of our API, which we will call “theialive”. We click on
the Create button to continue.
The API management window now appears, allowing us to define our API endpoints.
There are two options we can use: REST or RPC; we choose REST as we’re building direct services to our existing
business logic. We click on create a new one within the REST section of the window.
RPC is a good choice when your application requires tight coupling with the distributed architecture you build,
especially when you require quick and reliable communications between client and server(s). Most often you will
see payment processing, ordering systems, and medical applications preferring RPC over REST. Since this is not a
requirement for our use case, REST is our preferred choice.
New API window FIGURE 8
A dialogue screen pops up asking us to provide a New REST service popup FIGURE 10
name for our endpoint. We still can choose between
REST, RPC, and DB Connected. It’s true that all our data
is stored inside a database, but because we have a set
of business rules added to the retrieval of the data in
our web application, we’re going to choose the REST
endpoint.
Once we click on Create Service a skeleton REST service is generated for our endpoint. This window is where
we define what and how we will be retrieving our data.
Let’s have a look at the options we can see in this window. First of all there are General Settings that give you an
idea about routing, parameters, and HTTP verbs to allow. We can leave the default options as they are.
Content negotiation defines how we negotiate with the client. In our case, we used the default settings as they
were more than sufficient for our goal.
The benefit of this default is that it offers the content type application/hal+json out of the box. This allows
application developers to use content links and resources provided with the response to auto-discover these
links and resources.
For mobile Project general settings FIGURE 12
development,
this means the
API endpoints can
change, but the
application itself
doesn’t need to be
updated as it uses
the links provided
in the response
to figure out the
location of the
resources created
or used.
More information about Hypertext Application Language and the usage of application/hal+json can be
found at http://stateless.co/hal_specification.html.
Fields allows you define specific fields required in the client’s request. But as we’re only going to list our projects,
we don’t need to touch it now.
Authorization allows you to define specific ACLs on the endpoint and per HTTP verb. Since we’re going to use the
authorization from our original web application, we can skip this step, too.
API project authorization FIGURE 15
The Documentation tab is a great addition to Apigility as it allows you to create documentation about the
endpoint you’re managing. When a client connects to the endpoint and does not fulfill your specified
requirements, this documentation is returned in the error response to guide the user in making a successful
request.
Of course, this documentation is also a nice feature to have when you need to provide user documentation or an
application manual to the mobile app development team.
LISTING 3
37. <?php
38. /**
39. * Fetch a resource
40. *
41. * @param mixed $id
42. * @return ApiProblem|mixed
43. */
44. public function fetch($id) {
45. $project = new \Project_Model_Project();
46. $projectMapper = new \Project_Model_ProjectMapper();
47. $projectMapper->fetchRow($project, [
48. // Change later in proper authentication
49. 'accountId = ?' => 1,
50. 'projectId = ?' => $id,
51. ]);
52. return $project->toArray();
53. }
54.
55. /**
56. * Fetch all or a subset of resources
57. *
58. * @param array $params
59. * @return ApiProblem|mixed
60. */
61. public function fetchAll($params = array())
62. {
63. $projectCollection = new \Project_Model_Collection();
64. $projectMapper = new \Project_Model_ProjectMapper();
65. $projectMapper->fetchAll(
66. $projectCollection,
67. '\Project_Model_Project',
68. array (
69. // Change later in proper authentication
70. 'accountId = ?' => 1,
71. )
72. );
73. return $projectCollection->toArray();
74. }
In apigility/module/theialive/
src/theialive/V1/Rest/Project/
ProjectResource.php you need to locate two methods that require a code change:
• fetch($id)
• fetchAll($params = array())
Listing 3 contains the full change in detail.
After the source code has been changed, you can use a REST client to test the endpoints. Just point your client to
http://localhost:8888/project for the collection of projects and http://localhost:8888/project/4 to
see the details of the project with ID 4.
Authentication is probably something you need to see at the moment you start implementing it. We used a
user ID in the source code above, but we prefer to use an API token that is provided by Apigility’s authentication
management. However, authentication can quickly become very complex, so that’s
something you should figure out on your own. If
cURL call output FIGURE 19
you already have a solid authentication service update if it was older. It soon backfired, as some users
implemented, you can just tap into that service were not able to upgrade because of IT policies on
as we did with fetching project lists and details. their mobile devices. So we kept all versions active and
If not, you can choose to build one of your own backported security fixes to older versions.
or to use OAuth providers. The documentation
for Apigility has devoted a complete chapter on Deploying your Apigility REST API is similar to
choosing and implementing authentication. See deploying your other applications. One thing you
https://www.apigility.org/documentation/auth/intro need to watch out for is making sure you don’t deploy
for more information. it in development mode. By providing the following at
the command line you can disable development mode
Updating and creation of new entities is exactly the if required.
same, but instead of receiving data from a form on
the web application, the data now comes as a POST php public/index.php development disable
request with a JSON body. After json_decode you
have essentially the same data as you would get from Since Apigility is using the resources from your
your form, so it should be a breeze to implement that existing web application, it means that the server that
as well. runs Apigility requires the same access resources as
your web application. It wouldn’t be the first time to
have everything was ready to go, only to find out that
Important Considerations the web server running Apigility was not authorized to
access the database server.
Depending on your needs, you can run multiple
versions at the same time. But the moment a version
is released and in use by clients, you should be aware Further Steps
that you can’t break backwards compatibility. As long
as you don’t change the exposed endpoint behavior, I’ve become a passionate fan of Apigility, and thanks
you can continue development in the same version. to the efforts I made back in 2013 I’m adding REST
We didn’t want to take any risks and started a new services to legacy applications as an intermediate
version the moment a new version of the mobile app way to upgrade them from Zend Framework 1 to Zend
was released. Framework 2. But with DB Connected functionality I
can even attach Apigility to Java and .NET applications.
Running multiple versions allowed us to support
users who updated their mobile app to the latest If you like to know more about this or
version, but also users who are running an older have some questions, please contact me at
version of the app. Together with the external party [email protected]. I’m known not to respond
that developed the mobile app, we agreed that quickly, but your mail will get a response from me.
we would only support up to three versions Or come and see me at a PHP user group meeting or
and that the app would require an conference somewhere on this planet.
DisplayInfo()
Requirements:
• PHP: 5.3+
• Composer
• Symfony Console Component—http://phpa.me/symfony-console-component
• Symfony Yaml Component—http://phpa.me/symfony-yaml-component
Related URLs:
• Symfony Best Practices—http://phpa.me/symfony-best-practices
Introduction
Symfony is awesome! Why? Because it is a “set of [decoupled,] reusable components.” When these com-
ponents work together as a cohesive unit, they become applications that are easy to maintain and extend. By
using Symfony components, you can quickly create great applications without having to bootstrap an entire
framework. In this article, we will learn to use some components to empower you to write better applications
faster—starting with the Symfony Console.
To install it, use Composer: It is possible to define multiple arguments. If so, they
must be used in the same order in which they are
composer require symfony/console defined. The InputArgument::ARRAY mode is used
to allow any number of arguments. These arguments
Basic Console Concepts get parsed as an array, and for this to work correctly, it
must be defined as the last argument of the command.
There are two main classes that we use to assemble
a CLI application. The first one is the
Command class. Command is where we
LISTING 1
are going to define how commands are 01. <?php
configured (what arguments and options 02. //app
are available) and how they behave (the 03. require_once __DIR__.'/vendor/autoload.php';
code that is executed). 04.
05. use Symfony\Component\Console\Application;
The second is the Application class. 06. use Symfony\Component\Console\Command\Command;
It is used to add commands to the 07. use Symfony\Component\Console\Input\InputArgument;
application, and its responsibility is to run 08. use Symfony\Component\Console\Input\InputInterface;
the commands. 09. use Symfony\Component\Console\Output\OutputInterface;
10.
Creating a Simple Command 11. $say = function(InputInterface $in, OutputInterface $out) {
12. $phrase = $in->getArgument('phrase');
The first example is a simple say 13. $out->writeln($phrase);
program. The command takes an 14. };
argument, and it sends it to the given 15.
output. If there is no argument, 16. $sayCommand = new Command('say');
the command uses a default value: 17. $sayCommand->addArgument(
"I don't have anything to say". 18. 'phrase',
19. InputArgument::OPTIONAL,
The code needed for this simple 20. 'The phrase you want to say',
application is in Listing 1, which I’ve 21. 'I don\'t have anything to say');
saved as the file app. In line 16, a 22. $sayCommand->setCode($say);
new command object is instantiated. 23.
The name of the command is the first 24. $application = new Application();
argument of the constructor; as an 25. $application->add($sayCommand);
alternative, you can use the setName. 26. $application->run();
Command Arguments
The code that is executed by the command can be
Line 17 defines an argument for the command. set using the setCode method. This method only
Arguments are parameters that come after the accepts callables. Later in this article, an alternative
command name. The addArgument method takes four way to implement the code for the command will be
parameters: introduced. For now, this makes it simple to visualize
what is happening.
1. The argument name (required)
2. Argument mode: either The closure takes two parameters: $in (input) and
$out (output). The function uses the input object
• InputArgument::REQUIRED
to get the argument phrase that we defined in the
• InputArgument::OPTIONAL
command. Then the argument value is used to write it
• InputArgument::ARRAY
back to the screen using the output writeln method.
3. Argument description (optional)
4. Argument default value (optional) You can set custom input and output
objects by passing them as parameters of the
Application::run method.
NOTE: While the description parameter is optional,
you should make a mental note to always add it to all
of your arguments, as it is a good way to document
and remember what commands do.
CLEAN CODE: The parameters passed to the closure must be instances of classes that implement the
InputInterface and OutputInterface. While the command depends on input and output, it does not depend
on a single implementation. Instead, different implementations can be used (think of an output that can write to a log
file or one that suppresses all outputs), and the command code should continue to work.
Finally, we instantiate the application (line 24), add the command (line 25), and run it (line 26).
To execute the code from the command line, you can type the following:
project
├─ app
└─ src/
├─ Command/
│ ├─SayCommand.php
│ └─OtherCommands.php
└─ Resources/
├─files/
└─config/
{
"require": { ... },
"autoload": {
"psr-4": {
"PHPArch\\": "src/"
}
}
}
LISTING 2
class, rather than what we did in Listing 1. Mainly,
the command is contained in a single place where it
01. <?php
would be easier to maintain, test, and reuse.
02. // src/Command/SayCommand.php
03. namespace PHPArch\Command;
Listing 2 shows the same code from Listing 1, but it is
04.
now in the new class SayCommand.
05. use Symfony\Component\Console\Command\Command;
06. use Symfony\Component\Console\Input\InputArgument;
We place the code to configure the command in a
07. use Symfony\Component\Console\Input\InputInterface;
convenient method called configure and the code for
08. use Symfony\Component\Console\Output\OutputInterface;
the command in the execute method.
09.
10. class SayCommand extends Command
The app file can be improved by removing the extra
11. {
code and adding the brand-new command to the
12. protected function configure()
application.
13. {
14. $this->setName('say')
LISTING 3 15. ->setDescription('Write a short quote.')
16. ->addArgument(
01. <?php
17. 'phrase',
02. //app
18. InputArgument::OPTIONAL,
03. require_once __DIR__.'/vendor/autoload.php';
19. 'The phrase you want to say',
04.
20. 'I don\'t have anything to say'
05. use PHPArch\Command\SayCommand;
21. );
06. use Symfony\Component\Console\Application;
22. }
07.
23.
08. $application = new Application();
24. public function execute(
09. $application->add(new SayCommand());
25. InputInterface $input,
10. $application->run();
26. OutputInterface $output)
27. {
Testing the Say Command 28. $phrase = $input->getArgument('phrase');
The class can easily be tested by using the tools 29. $output->writeln($phrase);
provided by Symfony. The command tester is a 30. }
class that enables you to check if the output of the 31. }
command is correct.
LISTING 4
Listing 4 shows a simple PHPUnit test that
would go in a SayCommandTest class. 01. <?php
02. // test/TestSayCommand.php
Here, we use the application find method to 03. namespace PHPArch\Test;
get a command that gets passed to the tester. 04.
Then, we can use the PHPUnit assertRegExp 05. use PHPArch\Command\SayCommand;
method to test for the correct output of the 06. use Symfony\Component\Console\Application;
command. 07. use Symfony\Component\Console\Tester\CommandTester;
08.
09. class SayCommandTest extends \PHPUnit_Framework_TestCase
Creating a More 10. {
11.
Interesting Application 12.
public function testSayNothing()
{
In *nix systems, you can install a program 13. $application = new Application();
called Fortune. Fortune is a program that picks 14. $application->add(new SayCommand());
a “fortune” from a pool of quotes and prints it to 15.
the standard output. 16. $command = $application->find('say');
17. $tester = new CommandTester($command);
18. $tester->execute(['command' => $command->getName()]);
19. $this->assertRegExp(
20. '/I don\'t have anything to say/',
21. $tester->getDisplay());
22. }
23. }
34 | January 2016 www.phparch.com
Writing Better CLI Tools with Symfony Components
LISTING 5
While you may have interesting things to write in
01. <?php
the SayCommand, we are going to be a bit lazy and
02. // src/Command/FortuneCommand.php
automate the process of saying things.
03. namespace PHPArch\Command;
04.
A complete list of “fortunes” is
05. use Symfony\Component\Console\Command\Command;
available on a Github repository,
06. use Symfony\Component\Console\Input\ArrayInput;
https://github.com/bmc/fortunes/
07. use Symfony\Component\Console\Input\InputInterface;
blob/master/fortunes, as a file named fortunes.
08. use Symfony\Component\Console\Input\InputOption;
We are going to use it to build a new fortune
09. use Symfony\Component\Console\Output\OutputInterface;
command. Download it, and place it in the
10.
src/Resources directory.
11. class FortuneCommand extends Command
12. {
The fortune command is simple; it opens the
13. protected function configure() {
file, explodes it into a PHP array, and picks a
14. $this->setName('fortune')
random quote. Then the say command is used to
15. ->setDescription(
print out the fortune. Listing 5 shows the code for
16. 'A command to display random quotes.'
the new command.
17. )->addOption(
18. 'path-to-fortunes',
Don’t forget to add the FortuneCommand to the
19. 'p',
application in the app file:
20. InputOption::VALUE_REQUIRED,
// ...
21. 'Location of the fortunes archive',
use PHPArch\Command\FortuneCommand;
22. __DIR__.'/../Resources/fortunes'
23. );
// app
24. }
// ...
25.
26. public function execute(
$application->add(new FortuneCommand());
27. InputInterface $input,
$application->run();
28. OutputInterface $output) {
29. $pathToFortunes = $input->getOption('path-to-fortunes');
This command loads the fortunes from a default 30. $fortunes = explode('%',
or custom path in lines 30-31. Each quote is 31. file_get_contents($pathToFortunes));
separated by a % sign; we use the PHP explode 32. $count = count($fortunes)-1;
function to get an array of quotes and then select 33. $rand = rand(0, $count);
one at random. 34. $quote = $fortunes[$rand];
35.
A few important things to notice: 36. $sayCommand = $this->getApplication()->find('say');
37. $arrayInput = new ArrayInput(['phrase' => $quote]);
1. The command is not using any arguments; 38. $sayCommand->run($arrayInput, $output);
instead, we are adding an option called 39. }
path-to-fortunes. Options are explained 40. }
in more detail below. Command Options
2. The command say is used to send the fortunes to the The fortunes command introduces a new configuration
output. To have access to say, we need the application method: addOption. Here the option is used to load the
object that is available through the getApplication fortunes from a custom path or from the default location
method. within the Resources directory.
3. We do not use the same $input object (the one
passed to the execute method for the SayCommand), You can use options like flags (boolean), values, a
as it contains values for the FortuneCommand. combination of flags or values, and an array of values.
Instead, we use an alternative implementation of the Options can be used in any order we wish and are not
InputInterface where you can set command inputs required to run the command. Commands may contain any
as an associative array. number of arguments, options, or a combination of both.
NOTE: Just like argument descriptions, it is good to always add descriptions to the options.
Array Option
The input option VALUE_IS_ARRAY mode works differently from the argument ARRAY mode. With this one you
can add multiple values by defining the option multiple times like so:
Verbosity Levels
The Symfony Console enable
us to print messages using
the output object, but what
if we want to print out more
information or nothing at all?
The Console components
support five verbosity levels: quiet, normal, verbose, very verbose, and debug. These verbosity levels can be
enabled by using the following options:
1. Quiet: -q
2. Normal: No option required
3. Verbose: -v
4. Very Verbose: -vv
5. Debug: -vvv
The quickest way to check the current verbosity level is by using one of the following output methods:
$output->isQuiet()
$output->isVerbose() LISTING 7
$output->isVeryVerbose()
$output->isDebug() 01. $dir = __DIR__.'/../Resources/';
02. if ($output->isVerbose()) {
These methods return either 03. $output->writeln(file_get_contents($dir.'v_elephant'));
true or false. Add flow control 04. }
statements to print out custom 05. if ($output->isVeryVerbose()) {
messages for each verbosity level at 06. $output->writeln(file_get_contents($dir.'vv_elephant'));
the end of the execute method in the 07. }
ElephantFortuneCommand. 08. if ($output->isDebug()) {
09. $output->writeln(file_get_contents($dir.'vvv_elephant'));
Now try adding the verbose 10. }
option to the command:
php app elephant:fortune -vvv. If all goes as planned, you should see a herd of ASCII elephants.
Console Logger
If any of your command dependencies uses a PSR-3 logger, the Console component provides a ConsleLogger
class that sends all messages to the console output. By providing this class, we can ensure that the dependency
sends all log messages to the same place. You may need to require psr/log with Composer.
Then add the class your use statement in the command file:
use Symfony\Component\Console\Logger\ConsoleLogger;
Let’s improve the elephant fortune command by separating some concerns. Currently, we are loading different
files to display various types of elephants for each verbosity level. By moving that logic to a new class, we can
centralize the file-loading process from the src/Resources directory. The new class takes a PSR-3 logger and
displays a message before a file is loaded or if there is an issue loading the file.
// ...
// in execute()
$logger = new ConsoleLogger($output);
$loader = new FileLoader($logger);
$value = $loader->load('elephant');
$output->writeln($value);
if ($output->isVerbose()) {
$value = $loader->load('v_elephant');
$output->writeln($value);
}
// ...
LISTING 8
Now try out the improved
ElephantCommand: 01. <?php
php app elephant:fortune -vvv. 02. // src/FileLoader.php
03. namespace PHPArch;
04.
05. use Psr\Log\LoggerInterface;
CLEAN CODE: Use 06.
dependency injection 07. class FileLoader
as the means to acquire 08. {
dependencies in your objects. 09. private $logger;
The FileLoader takes a 10.
PSR-3 compliant logger; for this 11. public function __construct(LoggerInterface $logger) {
example, we use constructor 12. $this->logger = $logger;
injection. 13. }
14.
15. public function load($file) {
16. $this->logger->info("About to load file: $file");
Simple Command 17. $dir = __DIR__.'/Resources';
18.
Configurations 19. if(!file_exists("$dir/$file")) {
As a standalone component, the 20. $this->logger->error("Invalid $file");
Console does not provide a way to 21. $file = false;
add configurations, but we can use 22. } else {
other components to complement the 23. $file = file_get_contents("$dir/$file");
functionality of the commands. The 24. }
YAML component is the ideal 25.
candidate for helping to add basic 26. return $file;
configuration features. 27. }
28. }
NOTE: For advanced configuration features, use the Symfony Config component. With it, you can find and load
resources, define and validate config values, and more.
The YAML Symfony component implements most features of the YAML standard and is used mostly to parse
configuration files to PHP arrays.
To parse a file, you can use the convenient static method Yaml::parse.
use Symfony\Component\Yaml\Yaml;
if (file_exists($pathToConfig)) {
$yamlString = file_get_contents($pathToConfig);
$config = Yaml::parse($yamlString);
} else {
throw new \RuntimeException(
"The file $pathToConfig does not exist."
);
}
You can find additional information about YAML and how to use it in the Symfony documentation:
http://phpa.me/symfony-yaml
For this article, we are only going to use a configuration file to define the elephant that is displayed in the
elephant:fortune command. By doing so, you will get a basic idea of how to use configuration files in
commands.
# src/Resources/config/configuration.yml
#'small_elephant', 'medium_elephant'
elephant_file: elephant
Use the FileLoader we created earlier to retrieve the contents of the configuration file:
$yamlFile = $loader->load('config/configuration.yml');
$config = Yaml::parse($yamlFile);
$elephant = $loader->load($config['elephant_file'])
Try using the small_elephant or the medium_elephant included with the source code for the article by making
a simple modification to the config file—for example, see below:
elephant_file: small_elephant.
Helpers
The Console component has several helpers. These are useful classes that exist to improve and extend the
functionality of the application. From progress bars to tables, these classes are another set of features in the
console that you can use to create great applications.
While the helpers are outside the scope of this article, Listing 9 shows you how to use (what in my opinion is) the
most important helper to interact with a user: the QuestionHelper.
Best Practices
While the Console component helps you write better CLI applications more quickly, it can be abused just like
any other library. These are a few guidelines to help you write better CLI applications:
6. Think of commands as controllers in an MVC framework; they are the glue of your application, not a bowl of
spaghetti where all the logic lives.
7. Separate concerns and create single-responsibility commands.
8. Use other Symfony components to add additional functionality to your commands: Config,
EventDispatcher, StopWatch, and Process, just to name a few.
9. Write your code using interfaces, not concrete implementations. By doing so, you can extend the
functionality of the application without having to refactor existing code.
10. Add as much information as possible to the command configuration; your future self will be glad that you
did.
11. Use the setHelp method in your command class to add additional information about the command and
how to use it properly.
12. The Console is a robust component; read the documentation and find out more about many additional
features and how to extend it effectively.
Up Next
Next month, I will introduce the EventDispatcher component. This component will enable us to add and
extend the functionality of our commands without having to make modifications to the code. I have found this to
be one of the most helpful components that I have used, and it is a great complement for any PHP project.
JUAN MANUEL lives in San Diego, California where he works for MindTouch as
Senior Software Engineer. He started programming with PHP in 2000 as a
hobby. He has worked professionally with PHP for several years and has
developed different applications, tools and services. His passions include
photography, reptiles and SOLID programming. You can find him on Twitter
@onema.
Twitter: @onema
DisplayInfo()
Requirements:
• Raspberry Pi 2 Model B
Other Software:
• Raspbian (or other distro) Installed
Related URLs:
• Raspberry Pi Quickstart—http://phpa.me/raspberrypi-quickstart
• Linux Installation (using NOOBS)—http://phpa.me/raspberrypi-noobs
• Apache Server Setup—http://phpa.me/raspberrypi-apache
• NGINX Server Setup—http://phpa.me/raspberrypi-nginx
• Raspberry Pi 2 Model B Specifications—https://www.raspberrypi.org/?p=11476
• Apache vs. NGINX Performance Comparison—http://www.theorganicagency.com/?p=161
• Apache vs. NGINX Practical Considerations—http://phpa.me/do-apache-vs-nginx
• NGINX vs. Apache Blog Post—https://anturis.com/blog/nginx-vs-apache/
Introduction
Owning a Raspberry Pi 2 (also referred to as RasPi 4. HDMI-compatible Raspberry Pi 2 Model B
2) is fun and exciting, especially if you’ve never display (i.e. TV, PC
fiddled around with any of the older models. There monitor, etc.)
are countless activities and projects that the pint-size 5. Ethernet cable or wifi dongle
computer has the ability to manage. Among these
projects are running a Minecraft server, hooking up a 6. Internet connection
security camera, and even the comical sort such as a 7. Programmer necessities (i.e. soda, tunes, epic
Santa detector; the possibilities are endless! Of course, nachos, etc.)
what we are interested in is spinning up a PHP web
server, because that’s what we do. What You’ll Do
1. Use the terminal.
Now let’s not get too carried away with the amount
of stress we put the RasPi 2 through as there are 2. Install Apache or NGINX.
some limitations in terms of running a web server 3. Install PHP packages for your chosen web server.
on it. Looking at the technical specifications (refer to
4. Test your web server with HTML and PHP pages.
related links), you can see that a 900MHz quad-core
ARM Cortex-A7 CPU and 1GB of RAM may not be 5. Show your friends and family how cool you are.
able to handle the concurrency we’re used to from That’s everything you will need and everything you
other hosting setups. Of course any type of website will do in the scope of this little tutorial. However,
can be hosted on the RasPi 2, however, the Pi may as previously stated, there are many other projects
not offer the speed you need for the site. A better and adventures you can embark on with your RasPi
suited website to be hosted on the RasPi 2 is one that 2. I suggest taking a peek at what the Raspberry Pi
is informational or low-traffic at best. For example, a website has to offer.
portfolio or resumé site, or one you can only access on
a private network, is perfectly suited for the RasPi 2.
Web Server Setup
This article is a tutorial is meant to show you the
capabilities of the RasPi 2 as a web server. In following Choosing a Web server
it, you get the opportunity to play with your own RasPi
We won’t go too in-depth about the differences
2 web server and blow up Twitter with how much fun
between Apache and NGINX. The most important
you are having. That being said, the assumption is that
difference that I can make out from reading through
your RasPi 2 is already set up with some sort of Linux
blogs and forums is the way each architecture handles
distro because it will not be covered here (for help
concurrent processing.
with this, refer to related links).
In Figure 1, from the Organic Agency benchmarks in
Now that we got that out of the way…Let’s do this!
Apache vs. NGINX Performance Comparison, NGINX
seems to stand ahead with its speed in managing
Getting Started many concurrent requests, while Apache seems to
sometimes fail when the request count is high. If you’re
I know you’re itching to get started with your very feeling really wild, you could always use a combination
own home PHP web server, but let’s make sure of the two web servers. This setup is usually configured
you have what you need first. Without the proper with NGINX on the front end handling client requests,
equipment you will be a lost puppy, so pay close taking advantage of its fast processing speed, while
attention to the list below. Apache sits in the backseat as a reverse proxy. Since
we are creating a pretty basic website for our RasPi 2
What You’ll Need due to the hardware limitations, either server will do
just fine.
1. Raspberry Pi 2 Model B with Linux installed
(follow the Quick Start Guide at related URL 1,
Before we try to install either web server, let’s update
generally you will install the Raspbian flavor of
our package installer by running
Linux)
2. USB keyboard and mouse sudo apt-get update
3. HDMI cable
Once that update completes, we are ready to start
installing our web server.
cd /var/www
sudo nano index.html This sets up PHP using mod_php with Apache, which
is a very easy way to configure it. To use php-fm with
The Raspberry Pi will automatically start up the Apache, see http://phpa.me/linode-php-fpm.
NGINX web server on boot-up, so you don’t have to
worry about any extra setup for that.
The library takes only a second or so to install.
Once it completes, change directories into the
Apache root with cd /var/www. Run the command
sudo rm index.html, which will remove the index
page for your Apache server. Enter sudo nano index.php and we can now enter some PHP code that will be
served up on the Apache server. You can go with the classic first program of:
<?php phpinfo();
NGINX
NGINX requires a
different library to run
PHP but the install is just
as quick and easy. There
is a bit of additional
configuration for NGINX,
so pay close attention
here. We need the
php5-fpm for NGINX and
we will use apt-get to
install it.
When that completes, you can begin the additional setup by changing into the NGINX configuration directory
with cd /etc/nginx. Use the following command to begin editing the configuration file:
Now that the configuration file is open in nano, find the line that reads index index.html index.htm; (it
should be on line 25). Referring to Figure 8, change this line by adding index.php after index.
Now scroll down to line 62 and find more PHP configuration code. You’ll know you’re in the right place when,
two lines above, you see:
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
}
BOOM! NGINX is ready for action and set up to serve PHP. All that is left is to test it on the NGINX server. Change
into the NGINX root with cd /usr/share/nginx/www. Run the command sudo rm index.html and then
sudo nano index.php to create the PHP index page. Add some PHP code and save the file, then head over to
your web browser and enter your NGINX web address to observe the PHP magic!
Conclusion
That’s all it takes to install a web server, such as Apache or NGINX, that runs PHP! Congratulations on having
your own web server. Running a web server is a fantastic application of the Raspberry Pi 2, and what you do with
it is completely up to you! Try putting up your personal portfolio, running a WordPress site, or installing MySQL
and completing the LAMP stack. You could even run an ownCloud instance on your Raspberry Pi 2! No matter
what path you choose to forge, you can revel in the fact that you have set up a personal web server and it’s
running under your own roof!
DYLAN is a passionate web and mobile application developer with three years of
real world coding experience at Wise Agent. Having recently graduated from
Arizona State University with a degree in Computer Science and an emphasis in
Information Assurance, he looks forward to perfecting his skills on all web and
mobile platforms.
When he isn’t super glued to a keyboard, or tearing through millions of 0’s and
1’s, you could find him teaching the newest generation some wicked self-
defense moves.
Twitter: @dintorf
Welcome to Education Station for 2016. I hope you’re well rested and
recharged from the year that was and ready to crack on in 2016. If you
made any New Year’s resolutions, I hope you’ve not broken too many
of them already.
14
C
12 13
DisplayInfo()
Requirements
• Carbon—http://carbon.nesbot.com
• Composer—https://getcomposer.org
Anyway, with all that said, let’s get stuck in to another year of Education Station. To start off the year, I want to talk
about dates. No, not the very tasty and sugary fruit. Dates, as in days, years, months, weekends, and so on. You
know, the kind that, despite having had to work with them since we were all kids, we can still find challenging.
Now before I go too deep down this rabbit hole, I don’t want to give you the wrong impression. If you’ve been
working with any semi-recent version of PHP, you’ll have used its excellent DateTime extension. With it, we can do
all kinds of date calculations, as well as handle for time zones, intervals, and formatting.
But despite that, dates, date calculations, time zone offsets, and intervals can still be a challenge to work with.
Perhaps everyone else is fine and it’s just me. But I suspect I’m not the only one who would love to have them be
just that much simpler to work with. It shouldn’t take too much to guess where I’m heading with this, right?
As with all things in the life of a PHP developer, when you have an itch that needs scratching, you do one of two
things: you check if someone’s already made a package available, either via Packagist or GitHub, or you write one
yourself and make it available. So I went to Packagist and started doing some searching. Boy, what a wealth of
options appeared!
One option in particular caught my eye, and it’s what I’m going to be talking about this month. It’s called Carbon.
The name might, at first, seem kinda strange. If it does, I’ll leave one term with you—carbon dating. Still seem
strange? Anyway, enough of the long-winded introduction.
Carbon extends PHP’s DateTime extension making object creation, comparison, printing, modification, addition,
and subtraction a lot easier. What’s more, there are a series of further packages, which again extend Carbon’s
capabilities. There are extensions that calculate which days are British bank holidays, calculate the next business
day, and determine the end of the fiscal (financial) year. If you want to make working with dates, times, time zones,
and intervals easy, let’s dive in and see how.
Installing Carbon
As with all good PHP libraries, Carbon is installable via Composer. You can either add
"nesbot/carbon": "^1.21" to the relevant section of your composer.json and run composer update. Or you
can run:
If you don’t have Composer installed then, from the terminal in the root of your project, run:
That will download the Composer Phar file. After that, you can move Composer to a directory in your system path
and make it executable, by running the following command:
You could alternatively change the commands above to be php composer.phar, instead of composer. Either
way, let’s install the extensions I mentioned. I’ll assume that you have Composer installed in your system path for
the remainder of these commands. From your terminal, run the following commands:
<?php
require_once('vendor/autoload.php');
use Carbon\Carbon;
I could have been more specific and passed in a DateTimeZone object, like the following snippet shows, but it’s
not strictly necessary. I could also use the today method, but that leads to unexpected results when comparing
time differences between time zones.
Then let’s use the diffInHours method, along with the printf function to show the difference; see Listing 1.
They can show the difference between two Carbon objects in all kinds of ways, including hours, days, weeks,
weekends, week days, and even one that is perhaps a little less accurate, but more meaningful to us humans,
diffForHumans. Take a look at the example below where I’ve used them all:
Firstly, we created a new Carbon instance by specifying the year, month, and day, along with the Berlin time zone,
which we retrieved from $dtBerlin by calling its getTimezone method. Then, I called all the diff methods to
output the difference in the various ways. Running that, today, would give the following console output:
Most of these are pretty standard. The last one, which is human readable, to me is the most interesting. It’s
debatable as to whether this is necessary, as it’s not exactly precise. But it’s handy to have a way of interpreting a
date in a way we’re all more likely used to.
Date Formatting
Now that’s the setup taken care of, along with some simple calculations. What about formatting? As with date
differences, and the underlying class, there are a host of methods for formatting a Carbon object.
There are ones that return the date in various combinations of the day, month, and year, with or without a time
component. And there are a whole host of them for formatting based on various specifications and RFCs, such as
Atom, RSS, and Cookie. Here’s a sample of them:
Note that we’re not limited to just the available format functions, as Carbon extends DateTime, remember?
Technically, we could call DateTime’s format function directly if need be. Alternatively, if there’s a format that you
want to use repeatedly, you can call Carbon’s setToStringFormat() method, and pass in the desired format
string, then simply echo the object, which will invoke Carbon’s __toString() method. Here’s a quick example:
I hope you can see just how convenient the library is. If not, let’s have a look at a set of other functions. These are
the endOf*, firstOf*, is*, nthOf*, lastOf*, and startOf* methods. They are all handy methods for those
calculations that you might need, yet they can often be tricky to implement. In a nutshell, here’s how they work:
• endOf*, firstOf*, startOf*, and lastOf*: They find the relevant date for the century, date, decade,
month, week, or year (though not all of them support all these options).
• is*: This tests for a range of options, such as is the date a Friday, Monday, tomorrow, Tuesday, or a leap year.
• nthOf*: This finds the nth date within a month, quarter, or year.
Let’s have a look at a selection of them, starting with the is* methods. Given our $dtBerlin object, let’s ask the
following questions: Is the date yesterday, a Thursday, in the future, or a leap year? Here’s how we do it:
Share your
smarts with
learners
worldwide
Pluralsight is looking for tech and creative experts
to author online courses. Take part in a rewarding
experience that allows you to teach a global
audience, join a network of passionate, talented
peers, and supplement or replace your income.
Evolve your career at pluralsight.com/teach.
Now, what if we wanted to get the start and end of the current month, along with the end of the decade? Here’s
how:
Date Manipulation
Let’s round out the core functionality by doing some date manipulations. What if we wanted to see what 5 hours,
2 days, 1 week, and 3 months in the future is? Or what if we wanted to see what 4 years, 8 months, 7 hours in the
past was? For this, there are the add* and sub* methods. There’s one for all of the options you’d likely need. Let’s
see how to perform the manipulation I just mentioned:
Each of the add* and sub* methods returns a DateTime object, so implement the fluent interface, which allows
them to be chained together. Given that Carbon implements the __toString method, we can just chain them all
together and print the result. Doing so will give the following result (assuming you’ve not done both at the same
time):
That’s a good amount of coverage of the core library. Let’s finish up by having a look at two of the available
extensions, specifically the ones providing support for U.K. bank holidays and the fiscal year.
They’re a wonderful time to go for long weekends away, either somewhere in the U.K. or to Europe. Let’s look at
a couple of the functions, specifically showing the bank holidays for 2016–2017, and find out when the next bank
holiday is, so if you’re living in the U.K., you can get planning. Here’s how:
$dtLondon = CitcoCarbon::today('Europe/London');
list($date, $name) = each($dtLondon->nextBankHoliday());
printf("The next bank holiday is %s on %s\n", $name, $date);
We first include the Citco\Carbon namespace, aliasing it to CitcoCarbon, if we’re already using Carbon. Then,
as with Carbon, we instantiate a new Carbon object, specifying the London time zone. From there, we call the
nextBankHoliday method. This returns an array where the key is the date the holiday falls on and the value is the
holiday’s name. Using list, each, and printf, we can quickly extract and display the information. Now, what
about the full list of bank holidays for 2016–2017? Here’s how to get that:
We’ve called the getBankHolidays method and passed in an array of years. This returns an array like
nextBankHoliday does. So, this time we use a simple foreach loop to iterate over them. If you run this, you’ll get
output similar to the following:
use CarbonExt\FiscalYear\Calculator;
/* FY starts on July 1 */
$fyCalculator = new Calculator(7, 1);
print $fyCalculator->get($dtBerlin);
This would tell us that the end of our financial year is: Sunday 30th of June 2019.
Twitter: @settermjd
54 | January 2016 www.phparch.com
Leveling Up
be happy with it, the simple request for changing the infinitum. Our developer managed to take in the list
shade of green may turn into hours or days of back- of caregivers and combine the individual reports into
and-forth with the developer. Don’t laugh, I’ve seen one, but one particular issue was causing him fits. In
it happen. My former coworker ended up spending certain places in the report and at the end, a blank
nearly two days going back and forth with a business, page was inserted into the final PDF. We gathered
changing the size and color shade of a single character several developers and made all sorts of attempts to
on the site. Afterward, he decorated his cube with a get rid of these superfluous blank pages, but to no
large green asterisk drawn with a whiteboard marker avail. After a few days, we concluded that it was not
on paper and stapled to the wall. It was a testament going to allow us to remove these blank pages. We
to inefficiency. If you were told, as the owner of the reported this to the customer.
business, that hundreds or thousands of dollars had
been spent to get an asterisk to the right size and Their response was rather surprising. It didn’t matter
shade of green, it would be understandable if you to them because they were using this to print out each
were upset. of the individual calendars and then scan and email
them out to the individual caregivers. What?! Finally,
And yet, this sort of thing happens all the time. we had the real problem that needed a solution—the
electronic delivery of the calendars to the caregivers.
We could have simply iterated through the selected
PDF Combinatorics caregivers, generated each report in turn, and emailed
In a more recent example, the team was asked to the result to the caregiver. Instead, we were given a
allow a system to build a single large PDF report out potential “solution” but not given enough insight into
of what started as many individual small reports for a the problem to determine a better solution.
large number of medical caregivers. As it stood, the
system was capable of generating a calendar “report” I know that Agile software development practices
of each caregiver’s assignments for the month. The are typically either loved or hated, but regardless, the
request was to allow a user of our software to select standard story format does have a lot of value. “As
a bunch of caregivers and generate all of these a {role or user} I want {feature or change} so I can
individual calendars for use by the coordinators. {benefit}.” There’s a lot of potential value in a sentence
with that format. If the request above was spelled out
We were, and still are, using a reporting software this way, then it would have been the responsibility of
that allows us to build reports. One of the features is the developers to ask questions that would have led to
that reports can contain sub-reports, potentially ad the right solution.
in 2 it
build combined caregiver schedules so I can print,
scan, and email them to the caregivers without
contracting arthritis from too many mouse clicks.”
In this case, the majority of the valuable information
in the request is in the “benefit” or supposed
benefit. If the goal is to deliver the report to each
caregiver, then printing and scanning are not
PROFESSIONAL
PHP
SERVICES necessary. The amount of effort could have been
reduced to selecting a list of caregivers and clicking
a button. The system could then have generated
PHP
Consul,ng
Services
and delivered those calendars. Enhancing the
solution even further, depending on the selections,
most or potentially all of the work could have been
Workflow
automa,on
automated. If the caregivers are associated with the
coordinator and they all need their calendars on
a specific schedule, the selection could be made
Training
and
coaching
via a database query and the triggering of all of
this could be done via a cron job. The scheduling
coordinator now has one less thing to worry about
and we’ve delivered a real solution to the real
www.in2it.be
problem instead of an imagined solution to an
unexplained and misunderstood concern.
Other misunderstood requirements have led to such solving the problems, and architecting maintainable
erroneous features as load-bearing notes, exceedingly solutions.
complex data structures that allowed employees, copy
machines, meeting rooms, and MRI machines to be If we’re creating code for fun, then it’s up to us to
treated in exactly the same way. By taking the time to determine how much time and effort we want to
understand exactly what is needed and why, we get spend. At the time I’m writing this, the Advent of
a better solution, and we’ll often get it cheaper and Code is running. Each day presents a new challenge
faster as well. Speaking of which… which can be solved with code. According to the
leaderboard, many of these problems could be
completed within a few minutes by a small set of elite
The Iron Triangle of Software developers. If you know your language of choice well,
Development and potentially know an algorithm or already have
a library with an implementation, the problems can
Many of you may be familiar with the so-called “iron be solved relatively quickly. If you’re not familiar with
triangle” of software development. On each of the some technologies, it may take a bit longer.
sides of this triangle are “Good,” “Fast,” and “Cheap,”
or, in other versions, “Quality,” “Speed,” and “Cost.” In one of the problems, you’re given a list of a group
In any given software project, you’re only able to of people along with a happiness change score for
pick up to two of them. If we pick “Good” and “Fast,” each pair of people. For instance, if Bob sits next to
we’re going to need an excellent team, which will Sue, his happiness may increase by 50 points, but hers
not be cheap. If we pick “Good” and “Cheap,” we drops by 10. The problem wants you to determine
can get something like open-source software: people the optimal seating of people around a table so that
volunteering what time they can, mostly for free. The the maximum increase in happiness is achieved. The
resulting software can be very good, but since it’s all solution I chose borrowed some permutation code
on volunteer time, we cannot dictate how long before from a previous day and generated all possible
it is done. If the choice is “Fast” and “Cheap,” perhaps permutations of the dinner guest order. Since this was
we’ve got a skilled team on a tight deadline or a team for fun, I decided to stop there. I was able to get the
of motivated beginners. Quality will likely suffer. correct answer. However, what I wrote was inefficient.
Sick of
the iron triangle. There’s no need for that.
In a circular seating arrangement, many of the After the code is cleaned up, the final step is “make
permutations are redundant. If everyone gets up it fast.” This means that we need to realize there is a
and moves a seat to the left, their happiness doesn’t trade-off in nearly everything we do. Sometimes we
change because they are still sitting next to the same trade runtime for memory. Other times, we might
people. If you flip the ordering from clockwise to call out to a custom C-compiled extension in order to
counter-clockwise, all of the guests are still next to speed things up, but perhaps at the expense of ease
the same people and their happiness is unchanged. of maintenance.
However, my solution didn’t account for this. This
means that with an eight-person table, my code Of course, the “make it right” and “make it fast” steps
was checking 40,320 possibilities. If it accounted are much easier and less scary if you’ve written tests
for rotations, only 5,040 possibilities would have in order to make it work. Whether you follow a TDD
remained. Since I was running this code once, and workflow (which I recommend) or you write the tests
then likely never again, the runtime being 8x over an afterward, having those tests in place as you refactor
optimized solution wasn’t that big of a deal (though will allow you to be confident that you haven’t traded
it did bother me a bit). If I had code that did this as a in your working solution for a better looking, but
service for customers, or for larger tables, the amount broken, solution.
of time needed to update the algorithm to only
generate what was needed could mean, on a popular
service, I may only need 1/8th of the servers and Why Being DRY is Not Always
resources. In that case, it makes sense to optimize. Best
As software developers, we have loads of acronyms
Make it Work, Make it Right, to help us remember various principles. There’s SOLID
(ironically, each letter in SOLID stands for its own
Make it Fast acronym), KISS (keep it simple, stupid), KICK (keep it
Once we’ve determine what to build and why, our complex, knucklehead), DRY (don’t repeat yourself),
next task is to make it happen. This means our first YAGNI (you ain’t gonna need it), GRASP (general
pass at the code can be messy, inefficient, and, to responsibility assignment software pattern), DDD
a certain extent, bad. But we should certainly not (domain-driven development), as well as DRY (don’t
stop at this phase. Once it is working, we move to repeat yourself).
the next step, “make it right.” This means we clean
up the code, put things right. In my throwaway code We are often taught that DRY is important. We are
above, I stopped after making it work. I got the answer told that if two bits of code are the same, they should
I needed. However, in our day jobs or hobbies, if we go in a function. If two bits are nearly the same, put the
want code that will last, we need to continue. Making common bits in a function, abstract out the variable
it work involves understanding the problem at a code parts to the parameters, lather, rinse, repeat. This
level—perhaps reaching the point that you know what means that often in my career I’ve seen designs and
libraries are needed, how the inputs fit together, even software go sideways when what was initially
understanding that if you’re dealing with a circular determined to be two different pieces of code doing
permutation, there are a lot fewer possibilities than a the same thing, followed by refactoring that to a
standard permutation. function, turns out to be two bits of code doing nearly
the same thing but for completely different reasons. In
The “make things right” phase is where refactoring that case, the redundant code is arguably less bad
can happen. You can DRY (don’t repeat yourself) than a single complex solution that must account for a
out your code by moving repeated functionality into plethora of different running conditions.
functions and methods, renaming variables and
functions to be more clear about what’s happening, “Duplication is far cheaper than the wrong
potentially simplifying what’s going on since our abstraction.”
understanding of the problem has likely increased. We
- Sandi Metz
can reorganize to make it easier to maintain. This can
include tasks like removing calls to creating objects
with new inside our constructors and providing those In some cases, DRYing out our code can actually be
objects via dependency injection. harmful. Just because two bits of code appear to be
doing the same thing doesn’t mean they are. The
quote above is by Sandi Metz, an outstanding speaker
and software engineer. I encourage you to find her videos and watch them. She is a Ruby developer, but the
talks I’ve seen apply no matter what language you’re working in. I highly recommend watching any of her talks or
reading what she has written.
The point here links back to the beginning. De-duplication of code just for the sake of having a lower score on
phpcpd serves no good purpose. It’s better to have identical code in a few places while we strive to understand
the real problem and determine the real solution than to DRY out the code to a point where we don’t understand
what’s really happening and modifications become costly and potentially impossible. Strive to make your code as
simple as you can, but no simpler.
Conclusion
I encourage you to look into the change requests, issues, tickets, stories, or whatever you may call them, and
determine what they are really asking. It’s our job as software developers to build solutions to real problems, not
just to turn one silly request into code after another. We are being paid to think more than we’re being paid to
type. This means we need to ask the right questions, dig deeper into understanding what we need to do, and
provide real solutions to the real problems our customers have. It’s not about changing the asterisk to another
shade of green, it’s about determining why that needs to happen and what problem is being solved.
Twitter: @dstockto
www.phparch.com/training
www.phparch.com January 2016 | 59
Security Corner
DisplayInfo()
Related URLs:
• PHP Password Hashing–http://php.net/book.password
So how can I do this effectively in my application? Fortunately, it’s been made super simple since PHP 5.5 with
the password hashing functions. It’s literally a two-line process to hash what the user gives you and verify if it’s
correct:
<?php
// To hash the password
$storeMe = password_hash($_POST['userInput'], PASSWORD_DEFAULT);
This method currently uses an algorithm called bcrypt that rehashes the string The cost affects how
provided a number of times based on the “cost” value. PHP’s default cost is 10 difficult a password hash
unless you define it as an option. is to crack and is meant
to increase as hardware
becomes more capable.
And Finally
Passwords are flawed, there’s no doubt about it. I mentioned tools and services that have come up around the
password ecosystem and that want to help remediate some of the risk associated with using passwords—two-
factor authentication and federated identity being two of the more popular options. These can do a lot to help
improve the overall security stance of the application and prevent other problems from happening, but with a
password at the core of it all, a lot of risk will still be involved.
If the only protection between your application and the outside world is a simple user-defined string of text, it
might be time to rethink and reinforce your systems. Trust me, a password just doesn’t cut it.
For the last 10+ years, CHRIS has been involved in the PHP community in one way or another.
These days he’s the Senior Editor of PHPDeveloper.org and lead author for Websec.io, a site
dedicated to teaching developers about security and the Securing PHP ebook series. He’s
written for several PHP publications and has spoken at conferences in both the U.S. and
Europe. He’s also an organizer of the DallasPHP User Group and the Lone Star PHP
Conference and works as an Application Security Engineer for Salesforce.
Twitter: @enygma
podcast I was going to host where I covered mostly How about the Dev Book Club?
architectural topics and so-called “best practices” of The Dev Book Club, http://devbookclub.org––which,
software design and development. It morphed into thank you, Joe, for the reminder that I need to get
a co-hosted show where we talk about developer that back in gear––was born out of a conversation
practices of all sorts. I think it’s an interesting show, but at ZendCon one year where we were talking about
I might be biased. Domain-Driven Design and the Red vs. Blue Book
question. If you aren’t familiar, there are two books
Was it easier or harder than you expected
about DDD, one with a red cover and one with a blue
to start a podcast? What have you gained or
cover. There is often discussion about which you
learned from the process?
should read, which first, etc. This same conversation
It was and is a little harder than I expected. We happened in real time at ZendCon and it got me
decided to start the show because we sat down in thinking, “What if we created a forum for developers
a hotel lobby at Midwest PHP 2014 and talked for from all over the world to read and discuss tech
about two hours with some other people about all books?” Hence, Dev Book Club was born.
sorts of things we were going through at the time. It
was a great conversation and I felt it was enjoyable The idea is to provide a low-friction forum for
to everyone listening and participating. But an actual discussing the ideas and practices presented in
podcast is a little more work than that. various tech-related books with peers from all levels
and situations. We pick a book, get a group together,
I have learned a lot and gained a lot of respect and meet regularly. There will be some more on Dev
for people who do this more regularly than me. Book Club coming soon.
Producing, hosting, marketing, planning, etc. a
podcast is not easy. But out of the podcast, just in What’s the PHP Community like in College
recording the shows, I have definitely refined my Station, Texas?
own practices. I guess you could say I like doing the College Station is a smaller town, home of Texas A&M
podcast because it teaches me things as much as it is University. The majority of the tech scene is from that
about me sharing my experiences. arena. However, there is a large group of WordPress
consultants and other language/framework groups as
well. That’s one thing I really like about the community
here: it’s very low-judgment and we tend to focus less
on languages and more on ideas that cross the barriers
of frameworks and languages.
Past Events
PHP Conference Brazil Day Camp 4 Developers
December 2–6, Osasco, Brazil December 18, Online
http://www.phpconference.com.br http://daycamp4developers.com
Upcoming Events
Ski PHP 2016 Midwest PHP 2016
January 14–15, Salt Lake City, UT March 4–5, Minneapolis, MN
http://www.skiphp.com http://2016.midwestphp.org
JOE DEVON has been developing in PHP since Version 3. He is an advisor or board member
at various companies, non-profits and conferences. He is Founding Partner of LAPHP
user group; Diamond, a Digital Agency in Venice; Television Four, a Digital Media startup
and Global Accessibility Awareness Day (#GAAD). He is passionate about Digital
Accessibility & encouraging the UX & Development Community to produce accessible
content. He blogs at http://dws.la/author/joedevon and may be reached on LinkedIn
http://linkedin.com/in/joedevon, Twitter http://twitter.com/joedevon and email at
[email protected].
Twitter: @ joedevon
www.phparch.com January 2016 | 65
MONTH IN REVIEW: DECEMBER HAPPENINGS
Brought to you in conjunction with <?PHPDeveloper.org
PHP Releases
PHP 7.0.1:
http://php.net/archive/2015.php#id2015-12-17-1
News
Community News: PHP 7 Has Arrived (and Everyone’s Talking About It)
The big news in the PHP ecosystem is the release of the stable version of PHP 7.0.0. This was officially released late
yesterday and the response has already been great. Members of the PHP community (and some companies) have
also posted about the release too.
http://phpdeveloper.org/news/23435
Python
So why does Python 3 exist? In one word: Unicode. However, as people who followed the PHP 6 days
That’s right, the same issue that plagued PHP 6 (and know, this comes with some issues. Primarily, it comes
more on that later). with performance hits that many people didn’t want.
So while the coders of Python originally assumed that
So in Python, a string and a blob of bytes were over time people would just start using the unicode
actually the exact same thing in memory. It was only if type instead of str, they didn’t.
you parsed them as type str or bytes that changed
how they were treated inside your program. This, of So the decision was made that for Python 3, it would
course, can lead to some interesting situations. To be 100% Unicode all the time. Just like PHP 6 was
meet the need of people wanting/needing to do going to be. In this case, with the implied statement
Unicode work inside Python, they created a separate of: “And we don’t care if this means that people will
type, unicode, which you could declare instead to stay on Python 2 because they don’t like that.”
mean that the variable was truly, 100%, definitely, text.
So now, the community for Python has fragmented,
with different groups clinging to 2 vs. 3.
DisplayInfo()
Related URLs:
• Why Python 3 exists—http://www.snarky.ca/why-python-3-exists
Perl PHP
For those of us with enough white hair on our Which leads us to PHP. In my opinion, PHP almost
heads, we can see parallels in Perl 6. Perl 6 is the suffered this same fate with the move from 5 to 6 to 7…
language update that has essentially never come. but we have some smart people on the internals team
At a conference in 2000, Larry Wall announced the who have kept this from happening.
concept of Perl 6 and that work would begin on it. To
this day, Perl 6 has never actually been released. In case you aren’t familiar with “What became of
PHP 6”: It was an attempt, very similar to Python 3
(and tangentially similar to Perl 6), to refactor the
To be pedantic, there is an
language completely to make it Unicode-compliant
implementation (Rakudo Perl) that is all throughout the language. Every place that used
under active development and can be strings would natively and consistently use Unicode.
used, but there has never really been
a true release of Perl 6, and Perl 5 A couple of things happened, however, which
scuttled this plan after many years in development.
continues to be the de facto version in
First of all, the choice was made to go UTF-16 instead
use. of UTF-8. This complicated a number of issues (and the
Web settled on UTF-8 as a default in the meantime).
From the beginning, the intent of Perl 6 was to Part of the problem is that C-libraries can’t natively
completely break backwards-compatibility with Perl handle UTF-8, and there is no direct indexing for UTF-
5—but at the same time still feel like a “Perl Language.” 16. So numerous decoding steps had to happen in
the code to make this work. This caused a number of
In Perl 6, we decided it would be better really crucial, everyday-use features of PHP to lag and
slow down: database access, input filtering, and string
to fix the language than fix the user. searches, to mention a few.
— Larry Wall In the end, the project was scrapped, and PHP
moved on. PHP 5.3 came out, adding in a number of
There is a lot of history, which I won’t cover. The effect features, many of which were back-ported from the
was that Perl 6 stagnated within the Perl community, work on PHP 6. Since then, we’ve followed a much
simply because it wasn’t a natural progression from more evolutionary approach. Now PHP 7 has come
Perl 5. Users stuck with the previous version because it and added some of the biggest new features that PHP
was meeting their needs, and Perl 5 has continued to has seen, such as static type hinting and return types.
iterate along that path (with their most recent release And through the collective powers of the internals
being Perl 5.22 in June 2015). team, it was smartly done in a very backwards-
compatible way.
Because of the drastic change in the language,
instead of a more natural evolution, the community
fragmented. While I don’t have the perfect chart of There are no shortcuts in evolution.
data to show you, anecdotally this relates to the time
frame (2000) when Perl really started to see a drop — Louis D. Brandeis
in usage, as more and more programmers started
moving over to Python and PHP at that time. Both There is no reason for someone to “Stay on PHP 5”
were growing and continuing to evolve. because they don’t like how the language changed.
That’s a good thing. It encourages a healthy upgrade
cycle. It keeps the community unified. Without a
natural evolution approach, splits in the community
(or even forks in the project) are inevitable. And that
would hurt us all.
Written by PHP professional and Zend Framework contributor, coach, and consultant Bart
McLeod, this book leverages his expertise to ease your application’s transition to Zend
Framework 2.
Purchase
http://phpa.me/ZFtoZF2
Borrowed this magazine?