[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/diviner/query/ -> DivinerAtomQuery.php (source)

   1  <?php
   2  
   3  final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {
   4  
   5    private $ids;
   6    private $phids;
   7    private $bookPHIDs;
   8    private $names;
   9    private $types;
  10    private $contexts;
  11    private $indexes;
  12    private $includeUndocumentable;
  13    private $includeGhosts;
  14    private $nodeHashes;
  15    private $titles;
  16    private $nameContains;
  17  
  18    private $needAtoms;
  19    private $needExtends;
  20    private $needChildren;
  21  
  22    public function withIDs(array $ids) {
  23      $this->ids = $ids;
  24      return $this;
  25    }
  26  
  27    public function withPHIDs(array $phids) {
  28      $this->phids = $phids;
  29      return $this;
  30    }
  31  
  32    public function withBookPHIDs(array $phids) {
  33      $this->bookPHIDs = $phids;
  34      return $this;
  35    }
  36  
  37    public function withTypes(array $types) {
  38      $this->types = $types;
  39      return $this;
  40    }
  41  
  42    public function withNames(array $names) {
  43      $this->names = $names;
  44      return $this;
  45    }
  46  
  47    public function withContexts(array $contexts) {
  48      $this->contexts = $contexts;
  49      return $this;
  50    }
  51  
  52    public function withIndexes(array $indexes) {
  53      $this->indexes = $indexes;
  54      return $this;
  55    }
  56  
  57    public function withNodeHashes(array $hashes) {
  58      $this->nodeHashes = $hashes;
  59      return $this;
  60    }
  61  
  62    public function withTitles($titles) {
  63      $this->titles = $titles;
  64      return $this;
  65    }
  66  
  67    public function withNameContains($text) {
  68      $this->nameContains = $text;
  69      return $this;
  70    }
  71  
  72    public function needAtoms($need) {
  73      $this->needAtoms = $need;
  74      return $this;
  75    }
  76  
  77    public function needChildren($need) {
  78      $this->needChildren = $need;
  79      return $this;
  80    }
  81  
  82  
  83    /**
  84     * Include "ghosts", which are symbols which used to exist but do not exist
  85     * currently (for example, a function which existed in an older version of
  86     * the codebase but was deleted).
  87     *
  88     * These symbols had PHIDs assigned to them, and may have other sorts of
  89     * metadata that we don't want to lose (like comments or flags), so we don't
  90     * delete them outright. They might also come back in the future: the change
  91     * which deleted the symbol might be reverted, or the documentation might
  92     * have been generated incorrectly by accident. In these cases, we can
  93     * restore the original data.
  94     *
  95     * However, most callers are not interested in these symbols, so they are
  96     * excluded by default. You can use this method to include them in results.
  97     *
  98     * @param bool  True to include ghosts.
  99     * @return this
 100     */
 101    public function withIncludeGhosts($include) {
 102      $this->includeGhosts = $include;
 103      return $this;
 104    }
 105  
 106    public function needExtends($need) {
 107      $this->needExtends = $need;
 108      return $this;
 109    }
 110  
 111    public function withIncludeUndocumentable($include) {
 112      $this->includeUndocumentable = $include;
 113      return $this;
 114    }
 115  
 116    protected function loadPage() {
 117      $table = new DivinerLiveSymbol();
 118      $conn_r = $table->establishConnection('r');
 119  
 120      $data = queryfx_all(
 121        $conn_r,
 122        'SELECT * FROM %T %Q %Q %Q',
 123        $table->getTableName(),
 124        $this->buildWhereClause($conn_r),
 125        $this->buildOrderClause($conn_r),
 126        $this->buildLimitClause($conn_r));
 127  
 128      return $table->loadAllFromArray($data);
 129    }
 130  
 131    protected function willFilterPage(array $atoms) {
 132      $books = array_unique(mpull($atoms, 'getBookPHID'));
 133  
 134      $books = id(new DivinerBookQuery())
 135        ->setViewer($this->getViewer())
 136        ->withPHIDs($books)
 137        ->execute();
 138      $books = mpull($books, null, 'getPHID');
 139  
 140      foreach ($atoms as $key => $atom) {
 141        $book = idx($books, $atom->getBookPHID());
 142        if (!$book) {
 143          unset($atoms[$key]);
 144          continue;
 145        }
 146        $atom->attachBook($book);
 147      }
 148  
 149      if ($this->needAtoms) {
 150        $atom_data = id(new DivinerLiveAtom())->loadAllWhere(
 151          'symbolPHID IN (%Ls)',
 152          mpull($atoms, 'getPHID'));
 153        $atom_data = mpull($atom_data, null, 'getSymbolPHID');
 154  
 155        foreach ($atoms as $key => $atom) {
 156          $data = idx($atom_data, $atom->getPHID());
 157          if (!$data) {
 158            unset($atoms[$key]);
 159            continue;
 160          }
 161          $atom->attachAtom($data);
 162        }
 163      }
 164  
 165      // Load all of the symbols this symbol extends, recursively. Commonly,
 166      // this means all the ancestor classes and interfaces it extends and
 167      // implements.
 168  
 169      if ($this->needExtends) {
 170  
 171        // First, load all the matching symbols by name. This does 99% of the
 172        // work in most cases, assuming things are named at all reasonably.
 173  
 174        $names = array();
 175        foreach ($atoms as $atom) {
 176          foreach ($atom->getAtom()->getExtends() as $xref) {
 177            $names[] = $xref->getName();
 178          }
 179        }
 180  
 181        if ($names) {
 182          $xatoms = id(new DivinerAtomQuery())
 183            ->setViewer($this->getViewer())
 184            ->withNames($names)
 185            ->needExtends(true)
 186            ->needAtoms(true)
 187            ->needChildren($this->needChildren)
 188            ->execute();
 189          $xatoms = mgroup($xatoms, 'getName', 'getType', 'getBookPHID');
 190        } else {
 191          $xatoms = array();
 192        }
 193  
 194        foreach ($atoms as $atom) {
 195          $alang = $atom->getAtom()->getLanguage();
 196          $extends = array();
 197          foreach ($atom->getAtom()->getExtends() as $xref) {
 198  
 199            // If there are no symbols of the matching name and type, we can't
 200            // resolve this.
 201            if (empty($xatoms[$xref->getName()][$xref->getType()])) {
 202              continue;
 203            }
 204  
 205            // If we found matches in the same documentation book, prefer them
 206            // over other matches. Otherwise, look at all the the matches.
 207            $matches = $xatoms[$xref->getName()][$xref->getType()];
 208            if (isset($matches[$atom->getBookPHID()])) {
 209              $maybe = $matches[$atom->getBookPHID()];
 210            } else {
 211              $maybe = array_mergev($matches);
 212            }
 213  
 214            if (!$maybe) {
 215              continue;
 216            }
 217  
 218            // Filter out matches in a different language, since, e.g., PHP
 219            // classes can not implement JS classes.
 220            $same_lang = array();
 221            foreach ($maybe as $xatom) {
 222              if ($xatom->getAtom()->getLanguage() == $alang) {
 223                $same_lang[] = $xatom;
 224              }
 225            }
 226  
 227            if (!$same_lang) {
 228              continue;
 229            }
 230  
 231            // If we have duplicates remaining, just pick the first one. There's
 232            // nothing more we can do to figure out which is the real one.
 233            $extends[] = head($same_lang);
 234          }
 235  
 236          $atom->attachExtends($extends);
 237        }
 238      }
 239  
 240      if ($this->needChildren) {
 241        $child_hashes = $this->getAllChildHashes($atoms, $this->needExtends);
 242  
 243        if ($child_hashes) {
 244          $children = id(new DivinerAtomQuery())
 245            ->setViewer($this->getViewer())
 246            ->withIncludeUndocumentable(true)
 247            ->withNodeHashes($child_hashes)
 248            ->needAtoms($this->needAtoms)
 249            ->execute();
 250  
 251          $children = mpull($children, null, 'getNodeHash');
 252        } else {
 253          $children = array();
 254        }
 255  
 256        $this->attachAllChildren($atoms, $children, $this->needExtends);
 257      }
 258  
 259      return $atoms;
 260    }
 261  
 262    private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
 263      $where = array();
 264  
 265      if ($this->ids) {
 266        $where[] = qsprintf(
 267          $conn_r,
 268          'id IN (%Ld)',
 269          $this->ids);
 270      }
 271  
 272      if ($this->phids) {
 273        $where[] = qsprintf(
 274          $conn_r,
 275          'phid IN (%Ls)',
 276          $this->phids);
 277      }
 278  
 279      if ($this->bookPHIDs) {
 280        $where[] = qsprintf(
 281          $conn_r,
 282          'bookPHID IN (%Ls)',
 283          $this->bookPHIDs);
 284      }
 285  
 286      if ($this->types) {
 287        $where[] = qsprintf(
 288          $conn_r,
 289          'type IN (%Ls)',
 290          $this->types);
 291      }
 292  
 293      if ($this->names) {
 294        $where[] = qsprintf(
 295          $conn_r,
 296          'name IN (%Ls)',
 297          $this->names);
 298      }
 299  
 300      if ($this->titles) {
 301        $hashes = array();
 302        foreach ($this->titles as $title) {
 303          $slug = DivinerAtomRef::normalizeTitleString($title);
 304          $hash = PhabricatorHash::digestForIndex($slug);
 305          $hashes[] = $hash;
 306        }
 307  
 308        $where[] = qsprintf(
 309          $conn_r,
 310          'titleSlugHash in (%Ls)',
 311          $hashes);
 312      }
 313  
 314      if ($this->contexts) {
 315        $with_null = false;
 316        $contexts = $this->contexts;
 317        foreach ($contexts as $key => $value) {
 318          if ($value === null) {
 319            unset($contexts[$key]);
 320            $with_null = true;
 321            continue;
 322          }
 323        }
 324  
 325        if ($contexts && $with_null) {
 326          $where[] = qsprintf(
 327            $conn_r,
 328            'context IN (%Ls) OR context IS NULL',
 329            $contexts);
 330        } else if ($contexts) {
 331          $where[] = qsprintf(
 332            $conn_r,
 333            'context IN (%Ls)',
 334            $contexts);
 335        } else if ($with_null) {
 336          $where[] = qsprintf(
 337            $conn_r,
 338            'context IS NULL');
 339        }
 340      }
 341  
 342      if ($this->indexes) {
 343        $where[] = qsprintf(
 344          $conn_r,
 345          'atomIndex IN (%Ld)',
 346          $this->indexes);
 347      }
 348  
 349      if (!$this->includeUndocumentable) {
 350        $where[] = qsprintf(
 351          $conn_r,
 352          'isDocumentable = 1');
 353      }
 354  
 355      if (!$this->includeGhosts) {
 356        $where[] = qsprintf(
 357          $conn_r,
 358          'graphHash IS NOT NULL');
 359      }
 360  
 361      if ($this->nodeHashes) {
 362        $where[] = qsprintf(
 363          $conn_r,
 364          'nodeHash IN (%Ls)',
 365          $this->nodeHashes);
 366      }
 367  
 368      if ($this->nameContains) {
 369        // NOTE: This CONVERT() call makes queries case-insensitive, since the
 370        // column has binary collation. Eventually, this should move into
 371        // fulltext.
 372  
 373        $where[] = qsprintf(
 374          $conn_r,
 375          'CONVERT(name USING utf8) LIKE %~',
 376          $this->nameContains);
 377      }
 378  
 379      $where[] = $this->buildPagingClause($conn_r);
 380  
 381      return $this->formatWhereClause($where);
 382    }
 383  
 384  
 385    /**
 386     * Walk a list of atoms and collect all the node hashes of the atoms'
 387     * children. When recursing, also walk up the tree and collect children of
 388     * atoms they extend.
 389     *
 390     * @param list<DivinerLiveSymbol> List of symbols to collect child hashes of.
 391     * @param bool                    True to collect children of extended atoms,
 392     *                                as well.
 393     * @return map<string, string>    Hashes of atoms' children.
 394     */
 395    private function getAllChildHashes(array $symbols, $recurse_up) {
 396      assert_instances_of($symbols, 'DivinerLiveSymbol');
 397  
 398      $hashes = array();
 399      foreach ($symbols as $symbol) {
 400        foreach ($symbol->getAtom()->getChildHashes() as $hash) {
 401          $hashes[$hash] = $hash;
 402        }
 403        if ($recurse_up) {
 404          $hashes += $this->getAllChildHashes($symbol->getExtends(), true);
 405        }
 406      }
 407  
 408      return $hashes;
 409    }
 410  
 411  
 412    /**
 413     * Attach child atoms to existing atoms. In recursive mode, also attach child
 414     * atoms to atoms that these atoms extend.
 415     *
 416     * @param list<DivinerLiveSymbol> List of symbols to attach children to.
 417     * @param map<string, DivinerLiveSymbol> Map of symbols, keyed by node hash.
 418     * @param bool True to attach children to extended atoms, as well.
 419     * @return void
 420     */
 421    private function attachAllChildren(
 422      array $symbols,
 423      array $children,
 424      $recurse_up) {
 425  
 426      assert_instances_of($symbols, 'DivinerLiveSymbol');
 427      assert_instances_of($children, 'DivinerLiveSymbol');
 428  
 429      foreach ($symbols as $symbol) {
 430        $symbol_children = array();
 431        foreach ($symbol->getAtom()->getChildHashes() as $hash) {
 432          if (isset($children[$hash])) {
 433            $symbol_children[] = $children[$hash];
 434          }
 435        }
 436        $symbol->attachChildren($symbol_children);
 437        if ($recurse_up) {
 438          $this->attachAllChildren($symbol->getExtends(), $children, true);
 439        }
 440      }
 441    }
 442  
 443    public function getQueryApplicationClass() {
 444      return 'PhabricatorDivinerApplication';
 445    }
 446  
 447  }


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1