sample code

This commit is contained in:
2022-10-13 11:55:19 -04:00
commit bde8aa5e5d
67 changed files with 6421 additions and 0 deletions
@@ -0,0 +1,9 @@
name: Dafter Stock API
description: Provides services to fetch stock related data from various third-party APIs.
core: 8.x
type: module
package: Daftar API
core_version_requirement: ^8 || ^9
version: 1.0.0
tags:
- api
@@ -0,0 +1,15 @@
services:
daftar.stock_api.alphavantage:
class: Drupal\daftar_stock_api\Service\AlphaVantageApiService
arguments:
- '@logger.factory'
daftar.stock_api.yahoo_parser:
class: Drupal\daftar_stock_api\Service\YahooParserService
arguments:
- '@logger.factory'
daftar.stock_api.yahoo_api:
class: Drupal\daftar_stock_api\Service\YahooApiService
arguments:
- '@logger.factory'
@@ -0,0 +1,10 @@
<?php
namespace Drupal\daftar_stock_api\Exception;
use Exception;
class StockApiException extends Exception {
}
@@ -0,0 +1,93 @@
<?php
namespace Drupal\daftar_stock_api\Service;
use Drupal\Core\Logger\LoggerChannelFactory;
use Drupal\Core\Site\Settings;
use Drupal\daftar_stock_api\Exception\StockApiException;
use Drupal\daftar_stock_api\StockApiServiceInterface;
class AlphaVantageApiService implements StockApiServiceInterface {
const API_URL = 'https://www.alphavantage.co';
const API_PATH = 'query';
const SETTINGS_KEY_STOCK_API_KEY = 'daftar_stock_api_key';
/**
* Logger Factory.
*
* @var \Drupal\Core\Logger\LoggerChannelFactory
*/
protected $logger;
/**
* Constructor.
* @param LoggerChannelFactory $loggerFactory
*/
public function __construct(LoggerChannelFactory $loggerFactory) {
$this->logger = $loggerFactory->get('daftar_stock_api');
}
/**
* {@inheritdoc}
*/
public function doSymbolLookup($symbol) {
$jsonData = $this->doGet($symbol, 'OVERVIEW');
if (!isset($jsonData['Symbol']) || $jsonData['Symbol'] !== $symbol) {
return [];
}
return $jsonData;
}
/**
* @param $symbol
* @param $function
* @param array $params
* @return null
* @throws StockApiException
*/
private function doGet($symbol, $function, array $params = []) {
if (!$symbol) {
throw new StockApiException('Missing symbol');
}
if (!$function) {
$this->logger->error('Missing Stock API function.');
return NULL;
}
$apiKey = Settings::get(static::SETTINGS_KEY_STOCK_API_KEY, '');
if (!$apiKey) {
$this->logger->error('Missing Stock API Key.');
return NULL;
}
$client = new \GuzzleHttp\Client([
'base_uri' => static::API_URL,
'verify' => FALSE, // ignore invalid ssl
]);
$options = [
'query' => [
'apikey' => $apiKey,
'function' => strtoupper($function),
'symbol' => strtoupper($symbol),
],
];
$response = $client->get(static::API_PATH, $options);
if ($response->getStatusCode() != '200') {
$this->logger->error('Invalid response received from the Stock API.');
return NULL;
}
return json_decode($response->getBody(), TRUE);
}
}
@@ -0,0 +1,207 @@
<?php
namespace Drupal\daftar_stock_api\Service;
use DateTime;
use DOMDocument;
use Drupal\Core\Logger\LoggerChannelFactory;
use Drupal\daftar_stock_api\Exception\StockApiException;
use Drupal\daftar_stock_api\StockApiServiceInterface;
use Symfony\Component\HttpFoundation\Response;
class YahooApiService implements StockApiServiceInterface {
const API_URL = 'https://query2.finance.yahoo.com';
const API_PATH = '/v7/finance';
const API_ENDPOINT_OPTIONS = '/options';
/**
* Logger Factory.
*
* @var \Drupal\Core\Logger\LoggerChannelFactory
*/
protected $logger;
/**
* Constructor.
* @param LoggerChannelFactory $loggerFactory
*/
public function __construct(LoggerChannelFactory $loggerFactory) {
$this->logger = $loggerFactory->get('dsa_yahoo_api');
}
/**
* @param string $symbol
* @param string $date
*
* {@inheritdoc}
*/
public function doSymbolLookup($symbol) {
throw new StockApiException("doSymbolLookup has not been implemented.");
}
/**
* @param string $symbol
* @param string $date
* @param string $type
*
* @return array
* @throws \Drupal\daftar_stock_api\Exception\StockApiException
*/
public function getOptionContract(string $symbol, string $date, string $type, $strike): array {
$data = $this->getOptionContracts($symbol, $date, $type);
if ($data['error']) {
return $data;
}
$error = FALSE;
$message = '';
$needle = NULL;
foreach ($data['contents'] as $contract) {
if ($contract['strike'] == $strike) {
$needle = $contract;
break;
}
}
if (!$needle) {
$error = TRUE;
$message = 'Contract with given strike price is not included.';
}
return [
'error' => $error,
'message' => $message,
'contents' => ($error) ? NULL : $needle,
];
}
/**
* @param string $symbol
* @param string $date
* @param string $type
*
* @return array
* @throws \Drupal\daftar_stock_api\Exception\StockApiException
*/
public function getOptionContracts(string $symbol, string $date, string $type): array {
$data = $this->getOptionChain($symbol, $date);
if ($data['error']) {
return $data;
}
$error = FALSE;
$message = '';
if (empty($data['contents'][$type])) {
$error = TRUE;
$message = "Option for given type ${$type} are not included.";
}
return [
'error' => $error,
'message' => $message,
'contents' => ($error) ? NULL : $data['contents'][$type],
];
}
/**
* @param string $symbol
* @param string $date
*
* @return array
* @throws \Drupal\daftar_stock_api\Exception\StockApiException
* @throws \Exception
*/
public function getOptionChain(string $symbol, string $date): array {
$function = sprintf('%s/%s', self::API_ENDPOINT_OPTIONS, $symbol);
$dateTime = new DateTime($date);
$timestamp = $dateTime->format('U');
$data = $this->doGet($function, [
'date' => $timestamp,
]);
$error = FALSE;
$message = '';
if ($data['response_code'] !== Response::HTTP_OK) {
$error = TRUE;
$message = 'Response not received.';
}
elseif ($data['contents']['optionChain']['error']) {
$error = TRUE;
$message = 'Error in response received.';
}
elseif (empty($data['contents']['optionChain']['result'][0]['expirationDates'])
|| empty($data['contents']['optionChain']['result'][0]['strikes'])) {
$error = TRUE;
$message = 'Option chain not included.';
}
elseif (!$this->findValue($timestamp, $data['contents']['optionChain']['result'][0]['expirationDates'])) {
$error = TRUE;
$message = 'Option chain for given date is not included.';
}
return [
'error' => $error,
'message' => $message,
'contents' => ($error) ? NULL : $data['contents']['optionChain']['result'][0]['options'][0],
];
}
/**
* @param $needle
* @param $haystack
*
* @return bool
*/
private function findValue($needle, $haystack): bool {
$haystackIndex = array_flip($haystack);
return isset($haystackIndex[$needle]);
}
/**
* @param string $function
* @param array $params
*
* @return array An associative array containing status code and contents
* from the response.
* @throws \Drupal\daftar_stock_api\Exception\StockApiException
*/
private function doGet(string $function, array $params = []): array {
if (!$function) {
throw new StockApiException('Missing required function.');
}
if (substr($function, 0, 1) !== '/') {
throw new StockApiException("Function must begin with '/'.");
}
$client = new \GuzzleHttp\Client([
'base_uri' => static::API_URL,
'verify' => FALSE, // ignore invalid ssl
]);
$options = [
'allow_redirects' => FALSE,
'query' => $params,
];
$response = $client->get(self::API_PATH . $function, $options);
if ($response->getStatusCode() != Response::HTTP_OK) {
$this->logger->error('Invalid response received from Yahoo Finance API. Received status code: ' . $response->getStatusCode());
}
return [
'response_code' => $response->getStatusCode(),
'contents' => json_decode($response->getBody()->getContents(), TRUE),
];
}
}
@@ -0,0 +1,110 @@
<?php
namespace Drupal\daftar_stock_api\Service;
use DOMDocument;
use Drupal\Core\Logger\LoggerChannelFactory;
use Drupal\daftar_stock_api\Exception\StockApiException;
use Drupal\daftar_stock_api\StockApiServiceInterface;
class YahooParserService implements StockApiServiceInterface {
const API_URL = 'https://finance.yahoo.com';
const API_QUOTE_FUNCTION = 'quote';
/**
* Logger Factory.
*
* @var \Drupal\Core\Logger\LoggerChannelFactory
*/
protected $logger;
/**
* Constructor.
* @param LoggerChannelFactory $loggerFactory
*/
public function __construct(LoggerChannelFactory $loggerFactory) {
$this->logger = $loggerFactory->get('daftar_stock_api_yahoo');
}
/**
* {@inheritdoc}
*/
public function doSymbolLookup($symbol) {
throw new StockApiException("doSymbolLookup has not been implemented.");
}
/**
* @param $symbol
*
* @return string|null
*/
public function getPrice($symbol): ?string {
$htmlData = $this->doGet($symbol, static::API_QUOTE_FUNCTION . '/' . $symbol);
if (!$htmlData) {
return NUll;
}
$doc = new DOMDocument();
$doc->loadHTML($htmlData);
$quoteHeaderInfo = $doc->getElementById('quote-header-info');
if (!$quoteHeaderInfo) {
$this->logger->error('Unable to parse HTML response. Price not found. Symbol: ' . $symbol);
return NULL;
}
$finStreamers = $quoteHeaderInfo->getElementsByTagName('fin-streamer');
if ($finStreamers->count() <= 0) {
$this->logger->error('Missing element in HTML response.');
}
/** @var \DOMElement $elem */
foreach ($finStreamers as $elem) {
$dataField = $elem->getAttribute('data-field');
if ($dataField !== 'regularMarketPrice') {
continue;
}
return $elem->getAttribute('value');
}
$this->logger->error('Missing element with price in HTML response.');
return NULL;
}
/**
* @param $symbol
* @param $function
* @param array $params
* @return string|null
*/
private function doGet($symbol, $function, array $params = []) {
if (!$function) {
$this->logger->error('Missing required function.');
return NULL;
}
$client = new \GuzzleHttp\Client([
'base_uri' => static::API_URL,
'verify' => FALSE, // ignore invalid ssl
]);
$options = [
'allow_redirects' => FALSE,
'query' => [
'p' => strtoupper($symbol),
],
];
$response = $client->get($function, $options);
if ($response->getStatusCode() != '200') {
$this->logger->error('Invalid response received from Yahoo. Received status code: ' . $response->getStatusCode());
return NULL;
}
return $response->getBody()->getContents();
}
}
@@ -0,0 +1,19 @@
<?php
namespace Drupal\daftar_stock_api;
use Drupal\daftar_stock_api\Exception\StockApiException;
interface StockApiServiceInterface {
/**
* @param $symbol
* @return null
* @throws StockApiException
*/
public function doSymbolLookup($symbol);
}