2023-07-09 14:27:41 +02:00
< ? php
/*
* This file is part of Part - DB ( https :// github . com / Part - DB / Part - DB - symfony ) .
*
* Copyright ( C ) 2019 - 2023 Jan Böhmer ( https :// github . com / jbtronics )
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < https :// www . gnu . org / licenses />.
*/
declare ( strict_types = 1 );
namespace App\Services\InfoProviderSystem\Providers ;
use App\Entity\Parts\ManufacturingStatus ;
2023-07-16 18:35:44 +02:00
use App\Services\InfoProviderSystem\DTOs\FileDTO ;
2023-07-16 17:10:48 +02:00
use App\Services\InfoProviderSystem\DTOs\ParameterDTO ;
2023-07-09 23:31:40 +02:00
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO ;
2023-07-16 17:10:48 +02:00
use App\Services\InfoProviderSystem\DTOs\PriceDTO ;
use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO ;
2023-07-09 14:27:41 +02:00
use App\Services\InfoProviderSystem\DTOs\SearchResultDTO ;
2023-07-16 03:07:53 +02:00
use App\Services\OAuth\OAuthTokenManager ;
2023-07-09 14:27:41 +02:00
use Symfony\Contracts\HttpClient\HttpClientInterface ;
class DigikeyProvider implements InfoProviderInterface
{
2023-07-16 03:07:53 +02:00
private const OAUTH_APP_NAME = 'ip_digikey_oauth' ;
2023-07-16 17:10:48 +02:00
//Sandbox:'https://sandbox-api.digikey.com'; (you need to change it in knpu/oauth2-client-bundle config too)
private const BASE_URI = 'https://api.digikey.com' ;
private const VENDOR_NAME = 'DigiKey' ;
2023-07-16 03:07:53 +02:00
private readonly HttpClientInterface $digikeyClient ;
2023-07-09 14:27:41 +02:00
2024-09-09 20:44:09 +02:00
/**
* A list of parameter IDs , that are always assumed as text only and will never be converted to a numerical value .
* This allows to fix issues like #682, where the "Supplier Device Package" was parsed as a numerical value.
*/
private const TEXT_ONLY_PARAMETERS = [
1291 , //Supplier Device Package
39246 , //Package / Case
];
2023-07-16 17:10:48 +02:00
2023-07-17 00:19:02 +02:00
public function __construct ( HttpClientInterface $httpClient , private readonly OAuthTokenManager $authTokenManager ,
private readonly string $currency , private readonly string $clientId ,
private readonly string $language , private readonly string $country )
2023-07-16 03:07:53 +02:00
{
//Create the HTTP client with some default options
$this -> digikeyClient = $httpClient -> withOptions ([
2023-07-16 17:10:48 +02:00
" base_uri " => self :: BASE_URI ,
2023-07-16 03:07:53 +02:00
" headers " => [
" X-DIGIKEY-Client-Id " => $clientId ,
2023-07-17 00:19:02 +02:00
" X-DIGIKEY-Locale-Site " => $this -> country ,
" X-DIGIKEY-Locale-Language " => $this -> language ,
2023-07-16 17:10:48 +02:00
" X-DIGIKEY-Locale-Currency " => $this -> currency ,
2023-07-16 03:07:53 +02:00
" X-DIGIKEY-Customer-Id " => 0 ,
]
]);
2023-07-09 14:27:41 +02:00
}
public function getProviderInfo () : array
{
return [
'name' => 'DigiKey' ,
'description' => 'This provider uses the DigiKey API to search for parts.' ,
'url' => 'https://www.digikey.com/' ,
2023-07-16 03:18:33 +02:00
'oauth_app_name' => self :: OAUTH_APP_NAME ,
2023-07-17 00:19:02 +02:00
'disabled_help' => 'Set the PROVIDER_DIGIKEY_CLIENT_ID and PROVIDER_DIGIKEY_SECRET env option and connect OAuth to enable.'
2023-07-09 14:27:41 +02:00
];
}
public function getCapabilities () : array
{
return [
ProviderCapabilities :: BASIC ,
ProviderCapabilities :: FOOTPRINT ,
ProviderCapabilities :: PICTURE ,
ProviderCapabilities :: DATASHEET ,
ProviderCapabilities :: PRICE ,
];
}
public function getProviderKey () : string
{
return 'digikey' ;
}
public function isActive () : bool
{
2023-07-16 03:18:33 +02:00
//The client ID has to be set and a token has to be available (user clicked connect)
2024-06-22 00:31:43 +02:00
return $this -> clientId !== '' && $this -> authTokenManager -> hasToken ( self :: OAUTH_APP_NAME );
2023-07-09 14:27:41 +02:00
}
public function searchByKeyword ( string $keyword ) : array
{
$request = [
'Keywords' => $keyword ,
2025-03-27 21:26:18 +01:00
'Limit' => 50 ,
'Offset' => 0 ,
'FilterOptionsRequest' => [
'MarketPlaceFilter' => 'ExcludeMarketPlace' ,
],
2023-07-09 14:27:41 +02:00
];
2025-03-27 21:26:18 +01:00
//$response = $this->digikeyClient->request('POST', '/Search/v3/Products/Keyword', [
$response = $this -> digikeyClient -> request ( 'POST' , '/products/v4/search/keyword' , [
2023-07-09 14:27:41 +02:00
'json' => $request ,
2023-07-16 03:07:53 +02:00
'auth_bearer' => $this -> authTokenManager -> getAlwaysValidTokenString ( self :: OAUTH_APP_NAME )
2023-07-09 14:27:41 +02:00
]);
$response_array = $response -> toArray ();
$result = [];
$products = $response_array [ 'Products' ];
foreach ( $products as $product ) {
2025-03-27 21:26:18 +01:00
foreach ( $product [ 'ProductVariations' ] as $variation ) {
$result [] = new SearchResultDTO (
provider_key : $this -> getProviderKey (),
provider_id : $variation [ 'DigiKeyProductNumber' ],
name : $product [ 'ManufacturerProductNumber' ],
description : $product [ 'Description' ][ 'DetailedDescription' ] ? ? $product [ 'Description' ][ 'ProductDescription' ],
category : $this -> getCategoryString ( $product ),
manufacturer : $product [ 'Manufacturer' ][ 'Name' ] ? ? null ,
mpn : $product [ 'ManufacturerProductNumber' ],
preview_image_url : $product [ 'PhotoUrl' ] ? ? null ,
manufacturing_status : $this -> productStatusToManufacturingStatus ( $product [ 'ProductStatus' ][ 'Id' ]),
provider_url : $product [ 'ProductUrl' ],
footprint : $variation [ 'PackageType' ][ 'Name' ], //Use the footprint field, to show the user the package type (Tape & Reel, etc., as digikey has many different package types)
);
}
2023-07-09 14:27:41 +02:00
}
return $result ;
}
2023-07-16 17:10:48 +02:00
public function getDetails ( string $id ) : PartDetailDTO
{
2025-03-27 21:26:18 +01:00
$response = $this -> digikeyClient -> request ( 'GET' , '/products/v4/search/' . urlencode ( $id ) . '/productdetails' , [
2023-07-16 17:10:48 +02:00
'auth_bearer' => $this -> authTokenManager -> getAlwaysValidTokenString ( self :: OAUTH_APP_NAME )
]);
2025-03-27 21:26:18 +01:00
$response_array = $response -> toArray ();
$product = $response_array [ 'Product' ];
2023-07-16 17:10:48 +02:00
$footprint = null ;
$parameters = $this -> parametersToDTOs ( $product [ 'Parameters' ] ? ? [], $footprint );
2025-03-27 21:26:18 +01:00
$media = $this -> mediaToDTOs ( $id );
// Get the price_breaks of the selected variation
$price_breaks = [];
foreach ( $product [ 'ProductVariations' ] as $variation ) {
if ( $variation [ 'DigiKeyProductNumber' ] == $id ) {
$price_breaks = $variation [ 'StandardPricing' ] ? ? [];
break ;
}
}
2023-07-16 17:10:48 +02:00
return new PartDetailDTO (
provider_key : $this -> getProviderKey (),
2025-03-27 21:26:18 +01:00
provider_id : $id ,
name : $product [ 'ManufacturerProductNumber' ],
description : $product [ 'Description' ][ 'DetailedDescription' ] ? ? $product [ 'Description' ][ 'ProductDescription' ],
2023-07-16 18:35:44 +02:00
category : $this -> getCategoryString ( $product ),
2025-03-27 21:26:18 +01:00
manufacturer : $product [ 'Manufacturer' ][ 'Name' ] ? ? null ,
mpn : $product [ 'ManufacturerProductNumber' ],
preview_image_url : $product [ 'PhotoUrl' ] ? ? null ,
manufacturing_status : $this -> productStatusToManufacturingStatus ( $product [ 'ProductStatus' ][ 'Id' ]),
2023-07-16 17:10:48 +02:00
provider_url : $product [ 'ProductUrl' ],
footprint : $footprint ,
2023-07-16 18:35:44 +02:00
datasheets : $media [ 'datasheets' ],
images : $media [ 'images' ],
2023-07-16 17:10:48 +02:00
parameters : $parameters ,
2025-03-27 21:26:18 +01:00
vendor_infos : $this -> pricingToDTOs ( $price_breaks , $id , $product [ 'ProductUrl' ]),
2023-07-16 17:10:48 +02:00
);
}
2023-07-09 14:27:41 +02:00
/**
* Converts the product status from the Digikey API to the manufacturing status used in Part - DB
* @ param string | null $dk_status
* @ return ManufacturingStatus | null
*/
2025-03-27 21:26:18 +01:00
private function productStatusToManufacturingStatus ( ? int $dk_status ) : ? ManufacturingStatus
2023-07-09 14:27:41 +02:00
{
2025-03-27 21:26:18 +01:00
// The V4 can use strings to get the status, but if you have changed the PROVIDER_DIGIKEY_LANGUAGE it will not match.
// Using the Id instead which should be fixed.
//
// The API is not well documented and the ID are not there yet, so were extracted using "trial and error".
// The 'Preliminary' id was not found in several categories so I was unable to extract it. Disabled for now.
2023-07-09 14:27:41 +02:00
return match ( $dk_status ) {
null => null ,
2025-03-27 21:26:18 +01:00
0 => ManufacturingStatus :: ACTIVE ,
1 => ManufacturingStatus :: DISCONTINUED ,
2 , 4 => ManufacturingStatus :: EOL ,
7 => ManufacturingStatus :: NRFND ,
//'Preliminary' => ManufacturingStatus::ANNOUNCED,
2023-07-09 14:27:41 +02:00
default => ManufacturingStatus :: NOT_SET ,
};
}
2023-07-09 23:31:40 +02:00
2023-07-16 18:35:44 +02:00
private function getCategoryString ( array $product ) : string
{
2025-03-27 21:26:18 +01:00
$category = $product [ 'Category' ][ 'Name' ];
$sub_category = current ( $product [ 'Category' ][ 'ChildCategories' ]);
2023-07-16 18:35:44 +02:00
2025-03-27 21:26:18 +01:00
if ( $sub_category ) {
//Replace the ' - ' category separator with ' -> '
$category = $category . ' -> ' . str_replace ( ' - ' , ' -> ' , $sub_category [ " Name " ]);
}
2023-07-16 18:35:44 +02:00
2025-03-27 21:26:18 +01:00
return $category ;
2023-07-16 18:35:44 +02:00
}
2023-07-16 17:10:48 +02:00
/**
* This function converts the " Parameters " part of the Digikey API response to an array of ParameterDTOs
* @ param array $parameters
* @ param string | null $footprint_name You can pass a variable by reference , where the name of the footprint will be stored
* @ return ParameterDTO []
*/
private function parametersToDTOs ( array $parameters , string | null & $footprint_name = null ) : array
{
$results = [];
$footprint_name = null ;
foreach ( $parameters as $parameter ) {
if ( $parameter [ 'ParameterId' ] === 1291 ) { //Meaning "Manufacturer given footprint"
2025-03-27 21:26:18 +01:00
$footprint_name = $parameter [ 'ValueText' ];
2023-07-16 17:10:48 +02:00
}
2025-03-27 21:26:18 +01:00
if ( in_array ( trim (( string ) $parameter [ 'ValueText' ]), [ '' , '-' ], true )) {
2024-04-15 22:33:27 +02:00
continue ;
}
2024-09-09 20:44:09 +02:00
//If the parameter was marked as text only, then we do not try to parse it as a numerical value
if ( in_array ( $parameter [ 'ParameterId' ], self :: TEXT_ONLY_PARAMETERS , true )) {
2025-03-27 21:26:18 +01:00
$results [] = new ParameterDTO ( name : $parameter [ 'ParameterText' ], value_text : $parameter [ 'ValueText' ]);
2024-09-09 20:44:09 +02:00
} else { //Otherwise try to parse it as a numerical value
2025-03-27 21:26:18 +01:00
$results [] = ParameterDTO :: parseValueIncludingUnit ( $parameter [ 'ParameterText' ], $parameter [ 'ValueText' ]);
2024-09-09 20:44:09 +02:00
}
2023-07-16 17:10:48 +02:00
}
return $results ;
}
/**
* Converts the pricing ( StandardPricing field ) from the Digikey API to an array of PurchaseInfoDTOs
* @ param array $price_breaks
* @ param string $order_number
* @ param string $product_url
* @ return PurchaseInfoDTO []
*/
private function pricingToDTOs ( array $price_breaks , string $order_number , string $product_url ) : array
2023-07-09 23:31:40 +02:00
{
2023-07-16 17:10:48 +02:00
$prices = [];
foreach ( $price_breaks as $price_break ) {
$prices [] = new PriceDTO ( minimum_discount_amount : $price_break [ 'BreakQuantity' ], price : ( string ) $price_break [ 'UnitPrice' ], currency_iso_code : $this -> currency );
}
return [
new PurchaseInfoDTO ( distributor_name : self :: VENDOR_NAME , order_number : $order_number , prices : $prices , product_url : $product_url )
];
2023-07-09 23:31:40 +02:00
}
2023-07-16 18:35:44 +02:00
/**
* @ param array $media_links
* @ return FileDTO [][]
* @ phpstan - return array < string , FileDTO [] >
*/
2025-03-27 21:26:18 +01:00
private function mediaToDTOs ( string $id ) : array
2023-07-16 18:35:44 +02:00
{
$datasheets = [];
$images = [];
2025-03-27 21:26:18 +01:00
$response = $this -> digikeyClient -> request ( 'GET' , '/products/v4/search/' . urlencode ( $id ) . '/media' , [
'auth_bearer' => $this -> authTokenManager -> getAlwaysValidTokenString ( self :: OAUTH_APP_NAME )
]);
$media_array = $response -> toArray ();
foreach ( $media_array [ 'MediaLinks' ] as $media_link ) {
2023-07-16 18:35:44 +02:00
$file = new FileDTO ( url : $media_link [ 'Url' ], name : $media_link [ 'Title' ]);
switch ( $media_link [ 'MediaType' ]) {
case 'Datasheets' :
$datasheets [] = $file ;
break ;
case 'Product Photos' :
$images [] = $file ;
break ;
}
}
return [
'datasheets' => $datasheets ,
'images' => $images ,
];
}
2024-04-15 22:33:27 +02:00
}