Merge pull request #1 from lambdacasserole/oo-refactor

Big refactor of PHP code to be more OO
This commit is contained in:
Saul Johnson
2017-08-06 23:18:10 +01:00
committed by GitHub
8 changed files with 414 additions and 112 deletions
+6
View File
@@ -66,3 +66,9 @@ phpunit.phar
# Compiled code
/web/js/
/web/css/
# Caches
/db/*.dat
# Keep files
!.gitkeep
+7 -1
View File
@@ -14,6 +14,12 @@
"twig/twig": "^1.24",
"mustangostang/spyc": "^0.5.1",
"intervention/image": "^2.4",
"guzzlehttp/guzzle": "^6.3"
"guzzlehttp/guzzle": "^6.3",
"lambdacasserole/condense": "^1.1"
},
"autoload": {
"psr-4": {
"Avogadrio\\": "src/"
}
}
}
Generated
+148 -1
View File
@@ -4,8 +4,71 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "5e740fc02164dd61e791ba1ae1a34860",
"content-hash": "414181a9b439757b16b4938d109a80bd",
"packages": [
{
"name": "defuse/php-encryption",
"version": "v2.1.0",
"source": {
"type": "git",
"url": "https://github.com/defuse/php-encryption.git",
"reference": "5176f5abb38d3ea8a6e3ac6cd3bbb54d8185a689"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/defuse/php-encryption/zipball/5176f5abb38d3ea8a6e3ac6cd3bbb54d8185a689",
"reference": "5176f5abb38d3ea8a6e3ac6cd3bbb54d8185a689",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"paragonie/random_compat": "~2.0",
"php": ">=5.4.0"
},
"require-dev": {
"nikic/php-parser": "^2.0|^3.0",
"phpunit/phpunit": "^4|^5"
},
"bin": [
"bin/generate-defuse-key"
],
"type": "library",
"autoload": {
"psr-4": {
"Defuse\\Crypto\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Hornby",
"email": "taylor@defuse.ca",
"homepage": "https://defuse.ca/"
},
{
"name": "Scott Arciszewski",
"email": "info@paragonie.com",
"homepage": "https://paragonie.com"
}
],
"description": "Secure PHP Encryption Library",
"keywords": [
"aes",
"authenticated encryption",
"cipher",
"crypto",
"cryptography",
"encrypt",
"encryption",
"openssl",
"security",
"symmetric key cryptography"
],
"time": "2017-05-18T21:28:48+00:00"
},
{
"name": "guzzlehttp/guzzle",
"version": "6.3.0",
@@ -257,6 +320,42 @@
],
"time": "2017-07-03T15:50:40+00:00"
},
{
"name": "lambdacasserole/condense",
"version": "v1.1",
"source": {
"type": "git",
"url": "https://github.com/lambdacasserole/condense.git",
"reference": "dcbe8d7ca5ee0909a0df7c47e493311d130cbe9d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/lambdacasserole/condense/zipball/dcbe8d7ca5ee0909a0df7c47e493311d130cbe9d",
"reference": "dcbe8d7ca5ee0909a0df7c47e493311d130cbe9d",
"shasum": ""
},
"require": {
"defuse/php-encryption": "^2.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Condense\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Saul Johnson",
"email": "saul.a.johnson@gmail.com"
}
],
"description": "Flat-file database in PHP.",
"time": "2017-01-30T14:35:04+00:00"
},
{
"name": "mustangostang/spyc",
"version": "0.5.1",
@@ -304,6 +403,54 @@
],
"time": "2013-02-21T10:52:01+00:00"
},
{
"name": "paragonie/random_compat",
"version": "v2.0.10",
"source": {
"type": "git",
"url": "https://github.com/paragonie/random_compat.git",
"reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/634bae8e911eefa89c1abfbf1b66da679ac8f54d",
"reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d",
"shasum": ""
},
"require": {
"php": ">=5.2.0"
},
"require-dev": {
"phpunit/phpunit": "4.*|5.*"
},
"suggest": {
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
},
"type": "library",
"autoload": {
"files": [
"lib/random.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com"
}
],
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
"keywords": [
"csprng",
"pseudorandom",
"random"
],
"time": "2017-03-13T16:27:32+00:00"
},
{
"name": "pimple/pimple",
"version": "v3.0.2",
-1
View File
@@ -2,5 +2,4 @@
twitter_url: ~ # The URL of your Twitter profile.
github_url: ~ # The URL of your GitHub profile.
chem_name_lookup_service: ~ # The URL of the service to use to look up chemical names.
sourire_service: ~ # The URL of the Sourire molecule rendering service.
View File
+112
View File
@@ -0,0 +1,112 @@
<?php
namespace Avogadrio;
use Intervention\Image\ImageManagerStatic as Image;
/**
* Provides a molecule rendering service via a Sourire installation.
*
* @author Saul Johnson <saul.a.johnson@gmail.com>
* @since 06/08/2017
* @package Avogadrio
*/
class MoleculeRenderer
{
private $sourireUrl;
/**
* Initializes a new instance of a molecule rendering service.
*
* @param string $sourireUrl the URL of the running Sourire server to use
*/
public function __construct($sourireUrl)
{
// Add missing forward slash if needed.
$this->sourireUrl = $sourireUrl;
if ($this->sourireUrl[strlen($this->sourireUrl) - 1] != '/') {
$this->sourireUrl .= '/';
}
// Configure GD as image driver.
Image::configure(array('driver' => 'gd'));
}
/**
* Renders a molecule.
*
* @param string $smiles the SMILES structure of the molecule to render
* @param string $color the color to render the molecule in (as a hex string without `#`)
* @return \Intervention\Image\Image
*/
public function renderMolecule($smiles, $color)
{
// Proxy into Sourire for molecule render.
$img = Image::make($this->sourireUrl . urlencode($smiles));
// Colorize molecule.
list($r, $g, $b) = sscanf($color, "%02x%02x%02x");
$unit = 100 / 255;
$img->colorize($unit * $r, $unit * $g, $unit * $b);
return $img; // Return molecule image.
}
/**
* Renders a molecule, scaled to a canvas size using a maximum proportion.
*
* @param string $smiles the SMILES structure of the molecule to render
* @param string $color the color to render the molecule in (as a hex string without `#`)
* @param int $canvasWidth the width of the canvas to scale the molecule to
* @param int $canvasHeight the height of the canvas to scale the molecule to
* @param float $proportion the maximum proportion of the canvas the molecule should occupy (in either direction)
* @return \Intervention\Image\Image
*/
public function renderScaledMolecule($smiles, $color, $canvasWidth, $canvasHeight, $proportion = 0.8)
{
// Render molecule at normal size.
$img = $this->renderMolecule($smiles, $color);
// Calculate proportions of canvas width.
$px = $img->getWidth() / $canvasWidth;
$py = $img->getHeight() / $canvasHeight;
// Resize in both directions to fit.
while ($px > $proportion || $py > $proportion) {
if ($px > $proportion) {
$factor = ($canvasWidth * $proportion) / $img->getWidth();
} else {
$factor = ($canvasHeight * $proportion) / $img->getHeight();
}
$img->resize($img->getWidth() * $factor, $img->getHeight() * $factor);
$px = $img->getWidth() / $canvasWidth;
$py = $img->getHeight() / $canvasHeight;
}
return $img;
}
/**
* Renders a molecule, scaled to a canvas size using a maximum proportion.
*
* @param string $smiles the SMILES structure of the molecule to render
* @param string $foreground the color to render the molecule in (as a hex string without `#`)
* @param string $background the color to render the background in (as a hex string without `#`)
* @param int $width the width of the image to render
* @param int $height the height of the image to render
* @return \Intervention\Image\Image
*/
public function renderMoleculeWithBackground($smiles, $foreground, $background, $width, $height)
{
// Set up background.
$img = Image::canvas($width, $height, "#$background");
// Render scaled molecule.
$molecule = $this->renderScaledMolecule($smiles, $foreground, $width, $height);
// Center on background.
$img->insert($molecule, 'center');
return $img;
}
}
+64
View File
@@ -0,0 +1,64 @@
<?php
namespace Avogadrio;
use Condense\Database;
use \GuzzleHttp\Client;
/**
* Provides a conversion service between compound names and SMILES strings.
*
* @author Saul Johnson <saul.a.johnson@gmail.com>
* @since 06/08/2017
* @package Avogadrio
*/
class SmilesConverter
{
/**
* The compound name/SMILES cache database.
*
* @var Database
*/
private $db;
/**
* Initializes a new instance of a conversion service between compound names and SMILES strings.
*
* @param Database $db the database in which to cache lookup results
*/
public function __construct($db) {
$this->db = $db;
}
/**
* Converts a compound name to SMILES notation.
*
* @param string $name the name of the compound
* @return null|string the SMILES notation for the named compound or null if not found
*/
public function nameToSmiles($name)
{
// Sanitize name for URLs.
$encoded = urlencode($name);
// Check if we've got the name cached already.
$cachedSmiles = $this->db->get('smiles', 'name', $encoded);
if ($cachedSmiles !== null) {
return $cachedSmiles;
}
// Convert chemical name to SMILES if we can using API.
$client = new Client(['verify' => false, 'exceptions' => false]);
$response = $client->request('GET', "https://cactus.nci.nih.gov/chemical/structure/$encoded/smiles");
// If request was successful.
if ($response->getStatusCode() === 200) {
$smiles = $response->getBody()->getContents();
$this->db->insert(['name' => $encoded, 'smiles' => $smiles]); // Cache name for future.
return $smiles;
}
// Request failed.
return null;
}
}
+77 -109
View File
@@ -1,149 +1,117 @@
<?php
require_once __DIR__.'/../vendor/autoload.php';
require_once __DIR__ . '/../vendor/autoload.php';
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Intervention\Image\ImageManagerStatic as Image;
// Configure GD as image driver.
Image::configure(array('driver' => 'gd'));
// Load config.
$config = Spyc::YAMLLoad(__DIR__.'/../config/config.yaml');
use Condense\Database;
use Avogadrio\SmilesConverter;
use Avogadrio\MoleculeRenderer;
$app = new Silex\Application();
// Uncomment the line below while debugging your app.
$app['debug'] = true;
// Load config.
$config = Spyc::YAMLLoad(__DIR__ . '/../config/config.yaml');
// Services.
$smilesConverter = new SmilesConverter(new Database('names', __DIR__ . '/../db'));
$moleculeRenderer = new MoleculeRenderer($config['sourire_service']);
// Twig initialization.
$loader = new Twig_Loader_Filesystem(__DIR__.'/../templates');
$twig = new Twig_Environment($loader, array(
'cache' => false //__DIR__.'/../cache',
));
/*
* Rendering functions.
*/
function renderMolecule($color, $smiles) {
// Proxy into Sourire for molecule render.
$molecule = Image::make('http://localhost:8080/molecule/' . urlencode($smiles));
// Colorize molecule.
list($r, $g, $b) = sscanf($color, "%02x%02x%02x");
$unit = 100 / 255;
$molecule->colorize($unit * $r, $unit * $g, $unit * $b);
return $molecule; // Return molecule image.
}
function renderScaledMolecule($canvasWidth, $canvasHeight, $color, $smiles) {
$molecule = renderMolecule($color, $smiles);
$proportion = 0.6;
$px = $molecule->getWidth() / $canvasWidth;
$py = $molecule->getHeight() / $canvasHeight;
while ($px > $proportion || $py > $proportion) {
if ($px > $proportion) {
$factor = ($canvasWidth * $proportion) / $molecule->getWidth();
} else {
$factor = ($canvasHeight * $proportion) / $molecule->getHeight();
}
$molecule->resize($molecule->getWidth() * $factor, $molecule->getHeight() * $factor);
$px = $molecule->getWidth() / $canvasWidth;
$py = $molecule->getHeight() / $canvasHeight;
}
return $molecule;
}
/**
* Converts a compound name to SMILES notation.
*
* @param array $config the configuration settings for the application
* @param string $name the name of the compound
* @return null|string the SMILES notation for the named compound or null if not found
*/
function nameToSmiles($config, $name) {
// Convert chemical name to SMILES if we can using API.
$client = new GuzzleHttp\Client(['verify' => false, 'exceptions' => false]);
$res = $client->request('GET',
str_replace('$name', urlencode($name), $config['chem_name_lookup_service']));
// If request successful return SMILES, otherwise null.
return $res->getStatusCode() == 200 ? $res->getBody() : null;
}
/*
* Route actions.
*/
/**
* Action for frontend route.
*/
$app->get('/', function () use ($twig, $config) {
return $twig->render('index.html.twig', $config);
});
$app->get('/api/smiles/{width}/{height}/{bgcolor}/{fgcolor}/{smiles}', function ($width, $height, $bgcolor, $fgcolor, $smiles) use ($twig, $config) {
// Set up background.
$img = Image::canvas($width, $height, "#$bgcolor");
// Render molecule.
$molecule = renderScaledMolecule($width, $height, $fgcolor, $smiles);
// Center on background.
$img->insert($molecule, 'center');
// Send image out
return new \Symfony\Component\HttpFoundation\Response(
$img->response('png'),
200,
['Content-Type' => 'image/png']
);
/**
* Action for SMILES wallpaper route.
*/
$app->get('/api/smiles/{width}/{height}/{background}/{foreground}/{smiles}',
function ($width, $height, $background, $foreground, $smiles) use ($moleculeRenderer) {
// Render molecule with background.
$image = $moleculeRenderer->renderMoleculeWithBackground($smiles, $foreground, $background, $width, $height);
// Return image to client.
return new Response($image->response('png'), 200, ['Content-Type' => 'image/png']);
});
$app->get('/api/name/{width}/{height}/{bgcolor}/{fgcolor}/{name}', function ($width, $height, $bgcolor, $fgcolor, $name) use ($app, $twig, $config) {
/**
* Action for molecule-only SMILES route.
*/
$app->get('/api/smiles/{width}/{height}/{color}/{smiles}',
function ($width, $height, $color, $smiles) use ($moleculeRenderer) {
// Render molecule only.
$image = $moleculeRenderer->renderScaledMolecule($smiles, $color, $width, $height);
// Return image to client.
return new Response($image->response('png'), 200, ['Content-Type' => 'image/png']);
});
/**
* Action for compound name wallpaper route.
*/
$app->get('/api/name/{width}/{height}/{background}/{foreground}/{name}',
function ($width, $height, $background, $foreground, $name) use ($app, $smilesConverter) {
// Convert chemical name to SMILES if we can.
$smiles = nameToSmiles($config, $name);
// Convert chemical name to SMILES if we can.
$smiles = $smilesConverter->nameToSmiles($name);
// Forward to SMILES route.
if ($smiles !== null) {
$smilesRequest = Request::create("/api/smiles/$width/$height/$bgcolor/$fgcolor/$smiles", 'GET');
return $app->handle($smilesRequest, HttpKernelInterface::SUB_REQUEST);
}
// Forward to SMILES route.
if ($smiles !== null) {
$smilesRequest = Request::create("/api/smiles/$width/$height/$background/$foreground/$smiles", 'GET');
return $app->handle($smilesRequest, HttpKernelInterface::SUB_REQUEST);
}
// Invalid chemical name.
return $app->abort(404, "Chemical name could not be converted to SMILES.");
// Invalid chemical name.
return $app->abort(404, "Chemical name could not be converted to SMILES.");
});
$app->get('/api/smiles/{width}/{height}/{color}/{smiles}', function ($width, $height, $color, $smiles) use ($app, $twig, $config) {
return new \Symfony\Component\HttpFoundation\Response(
renderScaledMolecule($width, $height, $color, $smiles)->response('png'),
200,
['Content-Type' => 'image/png']
);
/**
* Action for molecule-only compound name route.
*/
$app->get('/api/name/{width}/{height}/{color}/{name}',
function ($width, $height, $color, $name) use ($app, $smilesConverter) {
// Convert chemical name to SMILES if we can.
$smiles = $smilesConverter->nameToSmiles($name);
// Forward to SMILES route.
if ($smiles !== null) {
$smilesRequest = Request::create("/api/smiles/$width/$height/$color/$smiles", 'GET');
return $app->handle($smilesRequest, HttpKernelInterface::SUB_REQUEST);
}
// Invalid chemical name.
return $app->abort(404, "Chemical name could not be converted to SMILES.");
});
$app->get('/api/name/exists/{name}', function ($name) use ($config) {
return new JsonResponse(nameToSmiles($config, $name) === null ? false : true);
});
/**
* Action for checking if compound name exists.
*/
$app->get('/api/name/exists/{name}',
function ($name) use ($config, $smilesConverter) {
$app->get('/api/name/{width}/{height}/{color}/{name}', function ($width, $height, $color, $name) use ($app, $twig, $config) {
// Convert chemical name to SMILES if we can.
$smiles = nameToSmiles($config, $name);
// Forward to SMILES route.
if ($smiles !== null) {
$smilesRequest = Request::create("/api/smiles/$width/$height/$color/$smiles", 'GET');
return $app->handle($smilesRequest, HttpKernelInterface::SUB_REQUEST);
}
// Invalid chemical name.
return $app->abort(404, "Chemical name could not be converted to SMILES.");
// Return JSON response (just a lone boolean).
return new JsonResponse($smilesConverter->nameToSmiles($name) === null ? false : true);
});
$app->run();