From 878c5e232aa0a18d63bb6cc8a6b7491d0c8fb8da Mon Sep 17 00:00:00 2001 From: Andrew Waite Date: Wed, 26 Jan 2022 17:38:46 +0000 Subject: [PATCH 1/3] Ability to get data from domain_domains endpoint --- README.md | 1 + src/Client.php | 21 ++++++++ src/Data/Column.php | 5 ++ src/Data/Type.php | 1 + src/Model/Request.php | 36 ++++++++++++++ src/Model/requestDefinitions.json | 49 ++++++++++++++++++- test/Integration/AbstractIntegrationTest.php | 2 +- .../DomainAdwordsIntegrationTest.php | 2 +- .../DomainAdwordsUniqueIntegrationTest.php | 2 +- .../DomainOrganicIntegrationTest.php | 2 +- .../DomainRankHistoryIntegrationTest.php | 2 +- .../Integration/DomainRankIntegrationTest.php | 2 +- .../DomainRanksIntegrationTest.php | 2 +- .../DomainVsDomainsIntegrationTest.php | 27 ++++++++++ test/ResponseExample/domain_domains.txt | 11 +++++ 15 files changed, 157 insertions(+), 8 deletions(-) create mode 100644 test/Integration/DomainVsDomainsIntegrationTest.php create mode 100644 test/ResponseExample/domain_domains.txt diff --git a/README.md b/README.md index 2902b37..ad510f1 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ A PHP API client for the SEMrush API. ## Supported actions: * domain_ranks +* domain_domain * domain_rank * domain_rank_history * domain_organic diff --git a/src/Client.php b/src/Client.php index 6c647c9..8f915c8 100644 --- a/src/Client.php +++ b/src/Client.php @@ -178,6 +178,27 @@ public function getDomainRankHistory($domain, $options = []) return $this->makeRequest(Type::TYPE_DOMAIN_RANK_HISTORY, ['domain' => $domain] + $options); } + /** + * @param array $domains + * @param $options + * @return ApiResult + * @throws Exception + */ + public function getDomainVsDomains(array $includedDomains, array $excludedDomains, $options = []) + { + $domains = []; + + foreach ($includedDomains as $domain) { + $domains[] = '*|or|'.$domain; + } + + foreach ($excludedDomains as $domain) { + $domains[] = '-|or|'.$domain; + } + + return $this->makeRequest(Type::TYPE_DOMAIN_VS_DOMAIN, ['domains' => implode('|', $domains)] + $options); + } + /** * @param $domain * @param array $options diff --git a/src/Data/Column.php b/src/Data/Column.php index 40d1551..673f700 100644 --- a/src/Data/Column.php +++ b/src/Data/Column.php @@ -97,6 +97,11 @@ abstract class Column const COLUMN_SERP_FEATURE_KEYWORD_ALSO_ASK = 'FK21'; const COLUMN_SERP_FEATURE_KEYWORD_FAQ = 'FK22'; const COLUMN_SERP_FEATURE_KEYWORD_FLIGHTS = 'FK23'; + const COLUMN_FIRST_DOMAIN = 'P0'; + const COLUMN_SECOND_DOMAIN = 'P1'; + const COLUMN_THIRD_DOMAIN = 'P2'; + const COLUMN_FOURTH_DOMAIN = 'P3'; + const COLUMN_FIFTH_DOMAIN = 'P4'; /** * Get all the possible columns diff --git a/src/Data/Type.php b/src/Data/Type.php index 7aa5bab..9448ebf 100644 --- a/src/Data/Type.php +++ b/src/Data/Type.php @@ -13,6 +13,7 @@ abstract class Type const TYPE_DOMAIN_ORGANIC = "domain_organic"; const TYPE_DOMAIN_ADWORDS = "domain_adwords"; const TYPE_DOMAIN_ADWORDS_UNIQUE = "domain_adwords_unique"; + const TYPE_DOMAIN_VS_DOMAIN = "domain_domains"; const TYPE_ADVERTISER_PUBLISHERS = "advertiser_publishers"; const TYPE_ADVERTISER_DISPLAY_ADS = "advertiser_text_ads"; const TYPE_ADVERTISER_RANK = "advertiser_rank"; diff --git a/src/Model/Request.php b/src/Model/Request.php index fb7f815..121e81b 100644 --- a/src/Model/Request.php +++ b/src/Model/Request.php @@ -125,6 +125,10 @@ protected function validateOption($option, $value) $this->validateDomain($option, $value); break; + case "domains": + $this->validateDomains($option, $value); + break; + case "database": $this->validateDatabase($option, $value); break; @@ -218,6 +222,38 @@ protected function validateDomain($key, $domain) } } + /** + * Validate domain + * + * @param string $key + * @param string $domain + * @throws InvalidOptionException + */ + protected function validateDomains($key, $domains) + { + $parts = explode('|', $domains); + + for ($i = 0; $i < count($parts); $i++) { + switch ($i % 3) { + case 0: + if (!in_array($parts[$i], ['+', '-', '/', '*'])) { + throw new InvalidOptionException("[{$key}] contains an invalid sign [{ $parts[$i]}]"); + } + break; + case 1: + if (!in_array($parts[$i], ['or', 'ad'])) { + throw new InvalidOptionException("[{$key}] contains an invalid type - must be or or ad [{ $parts[$i]}]"); + } + break; + case 2: + if (!preg_match('/^[a-z0-9-.]+$/i', $parts[$i])) { + throw new InvalidOptionException("[{$key}] contains an invalid domain [{ $parts[$i]}]"); + } + break; + } + } + } + /** * Validate date * diff --git a/src/Model/requestDefinitions.json b/src/Model/requestDefinitions.json index 2c97d2a..1ef4e91 100644 --- a/src/Model/requestDefinitions.json +++ b/src/Model/requestDefinitions.json @@ -133,6 +133,52 @@ "COLUMN_SERP_FEATURE_KEYWORD_FLIGHTS" ] }, + "domain_domains": { + "required_fields": { + "type": "type", + "key": "string", + "domains": "domains", + "database": "database" + }, + "optional_fields": { + "display_date": "date", + "export_columns": "columns", + "display_sort": "string", + "display_limit": "integer", + "export_escape": "boolean" + }, + "preset_fields": { + "export_escape": "1" + }, + "default_columns": [ + "COLUMN_DOMAIN_KEYWORD", + "COLUMN_FIRST_DOMAIN", + "COLUMN_SECOND_DOMAIN", + "COLUMN_THIRD_DOMAIN", + "COLUMN_FOURTH_DOMAIN", + "COLUMN_FIFTH_DOMAIN", + "COLUMN_KEYWORD_ORGANIC_NUMBER_OF_RESULTS", + "COLUMN_KEYWORD_AVERAGE_CLICK_PRICE", + "COLUMN_KEYWORD_AVERAGE_QUERIES", + "COLUMN_KEYWORD_DIFFICULTY_INDEX", + "COLUMN_KEYWORD_COMPETITIVE_AD_DENSITY", + "COLUMN_KEYWORD_INTEREST" + ], + "valid_columns": [ + "COLUMN_DOMAIN_KEYWORD", + "COLUMN_FIRST_DOMAIN", + "COLUMN_SECOND_DOMAIN", + "COLUMN_THIRD_DOMAIN", + "COLUMN_FOURTH_DOMAIN", + "COLUMN_FIFTH_DOMAIN", + "COLUMN_KEYWORD_ORGANIC_NUMBER_OF_RESULTS", + "COLUMN_KEYWORD_AVERAGE_CLICK_PRICE", + "COLUMN_KEYWORD_AVERAGE_QUERIES", + "COLUMN_KEYWORD_DIFFICULTY_INDEX", + "COLUMN_KEYWORD_COMPETITIVE_AD_DENSITY", + "COLUMN_KEYWORD_INTEREST" + ] + }, "domain_rank": { "required_fields": { "type": "type", @@ -336,7 +382,8 @@ "COLUMN_DOMAIN_KEYWORD_NUMBER", "COLUMN_TIMESTAMP", "COLUMN_SERP_FEATURES_DOMAIN", - "COLUMN_SERP_FEATURES_KEYWORD" + "COLUMN_SERP_FEATURES_KEYWORD", + "COLUMN_KEYWORD_DIFFICULTY_INDEX" ] }, "domain_shopping": { diff --git a/test/Integration/AbstractIntegrationTest.php b/test/Integration/AbstractIntegrationTest.php index b4e34fc..8b4e629 100644 --- a/test/Integration/AbstractIntegrationTest.php +++ b/test/Integration/AbstractIntegrationTest.php @@ -1,7 +1,7 @@ setupResponse('domain_domains'); + $result = $this->client->getDomainVsDomains(['nike.com', 'adidas.com'], ['reebok.com'], ['database' => Database::DATABASE_GOOGLE_US]); + $this->verifyResult($result, 10); + + /** + * @var Row $row + */ + $row = $result[1]; + self::assertEquals(48, $row->getValue(Column::COLUMN_FIRST_DOMAIN)); + self::assertEquals(22, $row->getValue(Column::COLUMN_SECOND_DOMAIN)); + self::assertEquals(0, $row->getValue(Column::COLUMN_THIRD_DOMAIN)); + } +} \ No newline at end of file diff --git a/test/ResponseExample/domain_domains.txt b/test/ResponseExample/domain_domains.txt new file mode 100644 index 0000000..b6716ff --- /dev/null +++ b/test/ResponseExample/domain_domains.txt @@ -0,0 +1,11 @@ +Keyword;nike.com;adidas.com;reebok.com;Domain4 Pos;Domain5 Pos;Number of Results;CPC;Search Volume;Keyword Difficulty;Competition;Trends +shoes;7;29;0;0;0;3830000000;0.73;1220000;100.00;1.00;0.67,0.67,0.55,0.67,1.00,1.00,0.81,0.55,0.81,0.81,0.81,0.81 +slides;48;22;0;0;0;3700000000;2.44;1220000;100.00;0.21;1.00,1.00,0.45,0.30,0.37,0.81,1.00,1.00,0.81,0.81,0.81,0.67 +slide;85;75;0;0;0;3510000000;0.85;823000;97.00;0.15;1.00,0.82,0.67,0.82,0.82,0.82,0.82,0.45,0.37,0.45,0.67,0.82 +golf;86;59;0;0;0;4200000000;1.00;673000;100.00;0.07;0.55,0.45,0.45,0.45,0.67,0.67,1.00,0.82,0.82,0.82,0.67,0.55 +nike outlet;1;70;0;0;0;459000000;0.74;673000;80.00;0.98;0.82,1.00,0.67,0.55,0.82,0.82,0.67,0.55,0.67,0.67,0.45,0.45 +hoka shoes;44;56;0;0;0;3300000000;1.43;550000;79.00;1.00;0.45,0.45,0.55,0.55,0.82,0.82,0.82,0.82,1.00,1.00,1.00,1.00 +sneakers;2;30;0;0;0;2030000000;0.97;550000;98.00;0.97;0.67,0.67,0.55,0.45,0.67,0.82,0.67,0.67,0.82,1.00,0.82,0.82 +hoodie;16;12;0;0;0;1090000000;1.01;450000;71.00;1.00;1.00,1.00,0.55,0.45,0.45,0.37,0.30,0.24,0.37,0.45,0.55,0.82 +men;22;62;0;0;0;4360000000;0.62;450000;93.00;0.02;1.00,0.82,0.82,0.82,0.82,0.82,0.82,0.82,0.82,0.82,0.82,0.82 +sandals;71;68;0;0;0;1010000000;9.45;450000;92.00;1.00;0.24,0.24,0.30,0.30,0.67,1.00,1.00,1.00,0.82,0.67,0.45,0.37 From c8243ee242b224710f7fa27c6e29d7fa217e6ebf Mon Sep 17 00:00:00 2001 From: Andrew Waite Date: Wed, 26 Jan 2022 17:39:49 +0000 Subject: [PATCH 2/3] Ability to get data from domain_domains endpoint --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 8f915c8..721e22e 100644 --- a/src/Client.php +++ b/src/Client.php @@ -184,7 +184,7 @@ public function getDomainRankHistory($domain, $options = []) * @return ApiResult * @throws Exception */ - public function getDomainVsDomains(array $includedDomains, array $excludedDomains, $options = []) + public function getDomainVsDomains(array $includedDomains, array $excludedDomains = [], array $options = []) { $domains = []; From ce7fd36f067f63c3014d22521358895b3cd9cf6b Mon Sep 17 00:00:00 2001 From: Andrew Waite Date: Wed, 26 Jan 2022 17:53:42 +0000 Subject: [PATCH 3/3] Fixes for PR comments --- README.md | 2 +- src/Model/Request.php | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ad510f1..11dc608 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ A PHP API client for the SEMrush API. ## Supported actions: * domain_ranks -* domain_domain +* domain_domains * domain_rank * domain_rank_history * domain_organic diff --git a/src/Model/Request.php b/src/Model/Request.php index 121e81b..124b52d 100644 --- a/src/Model/Request.php +++ b/src/Model/Request.php @@ -228,20 +228,26 @@ protected function validateDomain($key, $domain) * @param string $key * @param string $domain * @throws InvalidOptionException + * + * Validates the format ||||||... */ protected function validateDomains($key, $domains) { $parts = explode('|', $domains); + if (count($parts) > 5 * 3) { // 5 domains max, each with 3 parts + throw new InvalidOptionException("[{$key}] contains too many domains"); + } + for ($i = 0; $i < count($parts); $i++) { switch ($i % 3) { case 0: - if (!in_array($parts[$i], ['+', '-', '/', '*'])) { + if (!in_array($parts[$i], ['+', '-', '/', '*'], true)) { throw new InvalidOptionException("[{$key}] contains an invalid sign [{ $parts[$i]}]"); } break; case 1: - if (!in_array($parts[$i], ['or', 'ad'])) { + if (!in_array($parts[$i], ['or', 'ad'], true)) { throw new InvalidOptionException("[{$key}] contains an invalid type - must be or or ad [{ $parts[$i]}]"); } break;