sample code
This commit is contained in:
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user