From cc7af9d5414d2d460b4773e7870ca9174f648164 Mon Sep 17 00:00:00 2001 From: Saul Johnson Date: Sun, 6 Aug 2017 19:42:46 +0100 Subject: [PATCH] Add smiles converter as service --- .gitignore | 6 ++ composer.json | 8 ++- composer.lock | 149 +++++++++++++++++++++++++++++++++++++++- db/.gitkeep | 0 src/SmilesConverter.php | 59 ++++++++++++++++ web/index.php | 42 ++++------- 6 files changed, 235 insertions(+), 29 deletions(-) create mode 100644 db/.gitkeep create mode 100644 src/SmilesConverter.php diff --git a/.gitignore b/.gitignore index 9606f2f..a0f71cd 100644 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,9 @@ phpunit.phar # Compiled code /web/js/ /web/css/ + +# Caches +/db/*.dat + +# Keep files +!.gitkeep \ No newline at end of file diff --git a/composer.json b/composer.json index 20ad5d4..be273af 100644 --- a/composer.json +++ b/composer.json @@ -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/" + } } } diff --git a/composer.lock b/composer.lock index 7c5088e..ad0589d 100644 --- a/composer.lock +++ b/composer.lock @@ -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", diff --git a/db/.gitkeep b/db/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/SmilesConverter.php b/src/SmilesConverter.php new file mode 100644 index 0000000..b714faf --- /dev/null +++ b/src/SmilesConverter.php @@ -0,0 +1,59 @@ + + * @since 06/08/2017 + * @package Avogadrio + */ +class SmilesConverter +{ + /** + * The compound name/SMILES cache database. + * + * @var Database + */ + private $db; + + 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; + } +} diff --git a/web/index.php b/web/index.php index 915b257..1ce9516 100644 --- a/web/index.php +++ b/web/index.php @@ -1,6 +1,6 @@ 'gd')); // Load config. -$config = Spyc::YAMLLoad(__DIR__.'/../config/config.yaml'); +$config = Spyc::YAMLLoad(__DIR__ . '/../config/config.yaml'); + +// Caching SMILES converter. +$smilesConverter = new SmilesConverter(new Database('names', __DIR__ . '/../db')); $app = new Silex\Application(); @@ -59,24 +65,6 @@ function renderScaledMolecule($canvasWidth, $canvasHeight, $color, $smiles) { 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. */ @@ -104,12 +92,12 @@ $app->get('/api/smiles/{width}/{height}/{bgcolor}/{fgcolor}/{smiles}', function ); }); -$app->get('/api/name/{width}/{height}/{bgcolor}/{fgcolor}/{name}', function ($width, $height, $bgcolor, $fgcolor, $name) use ($app, $twig, $config) { +$app->get('/api/name/{width}/{height}/{bgcolor}/{fgcolor}/{name}', function ($width, $height, $bgcolor, $fgcolor, $name) use ($app, $smilesConverter) { // Convert chemical name to SMILES if we can. - $smiles = nameToSmiles($config, $name); + $smiles = $smilesConverter->nameToSmiles($name); - // Forward to SMILES route. + // 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); @@ -127,14 +115,14 @@ $app->get('/api/smiles/{width}/{height}/{color}/{smiles}', function ($width, $he ); }); -$app->get('/api/name/exists/{name}', function ($name) use ($config) { - return new JsonResponse(nameToSmiles($config, $name) === null ? false : true); +$app->get('/api/name/exists/{name}', function ($name) use ($config, $smilesConverter) { + return new JsonResponse($smilesConverter->nameToSmiles($name) === null ? false : true); }); -$app->get('/api/name/{width}/{height}/{color}/{name}', function ($width, $height, $color, $name) use ($app, $twig, $config) { +$app->get('/api/name/{width}/{height}/{color}/{name}', function ($width, $height, $color, $name) use ($app, $twig, $config, $smilesConverter) { // Convert chemical name to SMILES if we can. - $smiles = nameToSmiles($config, $name); + $smiles = $smilesConverter->nameToSmiles($name); // Forward to SMILES route. if ($smiles !== null) {