Working with dota2-api version 2.2.1 and higher

I’m starting a series of posts about the work with a part of the Steam API using dota2-api. It will be some kind of cookbook rather than pure documentation (rus/eng). Wiki on the github will contain links to this posts.

As a developer, I think my code is simple, clear and «easy to understand» for others. These «others» often feel exactly the opposite. These contradictions cause the creation of all sorts of «guides», «manuals» and «recipes».

I won’t describe dota2-api install process. It’s the same for any composer-package. Just follow the link to get your API-key — #. The only important point is, if you plan to save some public matches (not league’s matches), you should remove the foreign key for tables leagues and matches (field called leagueid). If you don’t, the matches won’t be saved then. You should save the tournaments list to table leagues if you plan to store leagues matches.

Let’s start with a simple thing, i.e. loading data for a match. We need only a match_id. It can be got in the game’s client. We take some random public match — 2472644644.

require_once 'vendor/autoload.php';
use Dota2Api\Api;
Api::init('YOUR_API_KEY', array('localhost', 'root', 'password', 'db_name', ''));
$mapper = new Dota2Api\Mappers\MatchMapperWeb(2472644644);
/* @var Dota2Api\Models\Match */
$match = $mapper->load();

It looks pretty simple. However it is not. API not always returns game’s data. It can return even an empty result. We should try to reload it several times in this case:

Hereinafter, the code snippets continue already written segments (that is, $match is already defined earlier).


$retries = 3;
if (is_null($match) && $retries !== 0) {
    $match = $mapper->load();
if (is_null($match)) {
    echo 'API is truly down'.
    exit 1;

We try to load match three times. If $match remains empty, it means that API is truly down. Let’s see how to output the match data, when it’s already available via $match (instance of Dota2Api\Models\Match). All models have method get to access to the object’s properties.

$durationInSeconds = $match->get('duration');
echo gmdate('H:i:s', $durationInSeconds); // '00:50:10'
echo $match->get('start_time'); // '2016-06-30 19:46:07'
echo $match->get('radiant_score').' - '.$match->get('dire_score'); // '57 - 47'
echo $match->get('game_mode');   // 2
echo $match->get('lobby_type');  // 7
echo $match->get('radiant_win'); // 1
echo $match->get('cluster');     // 181

game_mode, lobby_type and cluster look not so clear — just unknown numbers. Dota2Api\Data contains classes to their interpretation.

lobbies = new Dota2Api\Data\Lobbies();
$matchLobbyType = $match->get('lobby_type');
echo $lobbies->getFieldById($matchLobbyType, 'name'); // 'Ranked'
$mods = new Dota2Api\Data\Mods();
$matchGameMode = $match->get('game_mode');
echo $mods->getFieldById($matchGameMode, 'name'); // 'Captain Mode'
$regions= new Dota2Api\Data\Regions();
$matchRegion = $match->get('cluster');
echo $regions->getFieldById($matchRegion, 'name'); // 'Russia'

So, we got match’s start time, duration, result, type, rank and region, it was played in. But, first of all, match means it’s players. As we know, 10 players are in the «classic» map (1×1 solomid — exception, which does not consider):

/* @var Dota2Api\Models\Slot[] */
$slots = $match->getAllSlots();
foreach($slots as $player_slot => $slot) {
    // some code for $slot processing
print_r(array_keys($slots)); // [0, 1, 2, 3, 4, 128, 129, 130, 131, 132]
$firstSlot = $slots[0];
echo 'KDA: ' .
     $firstSlot->get('kills') . '/' .
     $firstSlot->get('deaths') . '/' .
     $firstSlot->get('assists') . "\n"; // 'KDA: 16/6/8'
echo $firstSlot->get('level');          // 25
echo $firstSlot->get('last_hits');      // 432
echo $firstSlot->get('denies');         // 14
echo $firstSlot->get('gold_per_min');   // 692
echo $firstSlot->get('xp_per_min');     // 646
echo $firstSlot->get('hero_id');        // 1

You should keep in mind, that the array with slots has indexes that are not consecutive. So, foreach should be used to traverse it. Almost all data fields in the Dota2Api\Models\Slot are self-sufficient. Only hero_id, account_id and item_* differ. First is a hero identifier that player used. Second is a player identifier. Third group (item_0 - item_5) is player’s inventory. The code below shows how to transform this in human-readable format:

$heroes = new Dota2Api\Data\Heroes();
$heroId = $firstSlot->get('hero_id');
echo $heroes->getFieldById($heroId, 'localized_name'); // 'Anti-Mage'
echo $heroes->getImgUrlById($heroId); // ''
echo $heroes->getImgUrlById($heroId, false); // ''
$items = new Dota2Api\Data\Items();
$firstItem = $firstSlot->get('item_0');
$items->getFieldById($firstItem, 'name'); // 'bfury'
echo $items->getImgUrlById($firstItem); // '' - small image
echo $items->getImgUrlById($firstItem, false); // '' - big image

Inventory and hero data is now clear, but what should we do with a player? Unfortunately, player’s data (like nickname, steamid, profile_url) is available in another API-endpoint. One important moment — user may config it’s client to hide «matches history» from another players. API will return 4294967295 instead of the real account_id. This value is available as a constant in the Dota2Api\Models\Player::ANONYMOUS:

if ($firstSlot->get('account_id') == Dota2Api\Models\Player::ANONYMOUS) {
    echo 'Anonymous player';

Each hero has its unique list of abilities. Each player has its own vision how to build it’s hero. Slot instances also contain this data (hero level and used ability):

$abilities = new Dota2Api\Data\Abilities();
$buildDetails = $firstSlot->getAbilitiesUpgrade();
foreach($buildDetails as $level) {
    echo $level['level'] . ' ' . $abilities->getFieldById($level['ability'], 'name') . ' ' . $level['time'] . "\n";

You should be very careful with $level['time']. Time count starts together with the game and picks/bans time is included. This means that 1st level ability is learned on the 12th minute (according to API).

Viewed match was played with Captains Mode and it has a picks/bans phase. How can we get this information?

$picksBans = $match->getAllPicksBans();
foreach($picksBans as $order) {
    $team = $order['team'] == 0 ? 'radiant' : 'dire';
    $state = $order['is_pick'] == 1 ? 'pick' : 'ban';
    echo $team .' '.$state.' '.$heroes->getFieldById($order['hero_id'], 'localized_name')."\n";
/* Output:
    dire ban Lifestealer
    radiant ban Nature's Prophet
    dire ban Invoker
    radiant ban Sven
    dire pick Elder Titan
    radiant pick Lion
    radiant pick Axe
    dire pick Disruptor
    radiant ban Windranger
    dire ban Warlock
    radiant ban Slark
    dire ban Silencer
    radiant pick Anti-Mage
    dire pick Beastmaster
    radiant pick Death Prophet
    dire pick Weaver
    radiant ban Queen of Pain
    dire ban Zeus
    dire pick Viper
    radiant pick Winter Wyvern

We understand what game data can be received from the API and how to display it to the user in readable form. It is to long to address the API each time we need data. The better idea is to store it in the local DB and to use it while needed:

$dbMapper = new Dota2Api\Mappers\MatchMapperDb();
$dbMapper->save($match); // save to db
/* @var Dota2Api\Models\Match */
$matchFromDb = $dbMapper->load($match->get('match_id')); // load from db

The way to get data from $matchFromDb is the same to any match received from API.

An output format can be limited with your imagination only (and you may always look how it’s done in the or

, ,

Оставить комментарий

Top ↑ | Main page | Back