Path: blob/master/src/infrastructure/edges/query/PhabricatorEdgeQuery.php
13410 views
<?php12/**3* Load object edges created by @{class:PhabricatorEdgeEditor}.4*5* name=Querying Edges6* $src = $earth_phid;7* $type = PhabricatorEdgeConfig::TYPE_BODY_HAS_SATELLITE;8*9* // Load the earth's satellites.10* $satellite_edges = id(new PhabricatorEdgeQuery())11* ->withSourcePHIDs(array($src))12* ->withEdgeTypes(array($type))13* ->execute();14*15* For more information on edges, see @{article:Using Edges}.16*17* @task config Configuring the Query18* @task exec Executing the Query19* @task internal Internal20*/21final class PhabricatorEdgeQuery extends PhabricatorQuery {2223private $sourcePHIDs;24private $destPHIDs;25private $edgeTypes;26private $resultSet;2728const ORDER_OLDEST_FIRST = 'order:oldest';29const ORDER_NEWEST_FIRST = 'order:newest';30private $order = self::ORDER_NEWEST_FIRST;3132private $needEdgeData;333435/* -( Configuring the Query )---------------------------------------------- */363738/**39* Find edges originating at one or more source PHIDs. You MUST provide this40* to execute an edge query.41*42* @param list List of source PHIDs.43* @return this44*45* @task config46*/47public function withSourcePHIDs(array $source_phids) {48if (!$source_phids) {49throw new Exception(50pht(51'Edge list passed to "withSourcePHIDs(...)" is empty, but it must '.52'be nonempty.'));53}5455$this->sourcePHIDs = $source_phids;56return $this;57}585960/**61* Find edges terminating at one or more destination PHIDs.62*63* @param list List of destination PHIDs.64* @return this65*66*/67public function withDestinationPHIDs(array $dest_phids) {68$this->destPHIDs = $dest_phids;69return $this;70}717273/**74* Find edges of specific types.75*76* @param list List of PhabricatorEdgeConfig type constants.77* @return this78*79* @task config80*/81public function withEdgeTypes(array $types) {82$this->edgeTypes = $types;83return $this;84}858687/**88* Configure the order edge results are returned in.89*90* @param const Order constant.91* @return this92*93* @task config94*/95public function setOrder($order) {96$this->order = $order;97return $this;98}99100101/**102* When loading edges, also load edge data.103*104* @param bool True to load edge data.105* @return this106*107* @task config108*/109public function needEdgeData($need) {110$this->needEdgeData = $need;111return $this;112}113114115/* -( Executing the Query )------------------------------------------------ */116117118/**119* Convenience method for loading destination PHIDs with one source and one120* edge type. Equivalent to building a full query, but simplifies a common121* use case.122*123* @param phid Source PHID.124* @param const Edge type.125* @return list<phid> List of destination PHIDs.126*/127public static function loadDestinationPHIDs($src_phid, $edge_type) {128$edges = id(new PhabricatorEdgeQuery())129->withSourcePHIDs(array($src_phid))130->withEdgeTypes(array($edge_type))131->execute();132return array_keys($edges[$src_phid][$edge_type]);133}134135/**136* Convenience method for loading a single edge's metadata for137* a given source, destination, and edge type. Returns null138* if the edge does not exist or does not have metadata. Builds139* and immediately executes a full query.140*141* @param phid Source PHID.142* @param const Edge type.143* @param phid Destination PHID.144* @return wild Edge annotation (or null).145*/146public static function loadSingleEdgeData($src_phid, $edge_type, $dest_phid) {147$edges = id(new PhabricatorEdgeQuery())148->withSourcePHIDs(array($src_phid))149->withEdgeTypes(array($edge_type))150->withDestinationPHIDs(array($dest_phid))151->needEdgeData(true)152->execute();153154if (isset($edges[$src_phid][$edge_type][$dest_phid]['data'])) {155return $edges[$src_phid][$edge_type][$dest_phid]['data'];156}157return null;158}159160161/**162* Load specified edges.163*164* @task exec165*/166public function execute() {167if ($this->sourcePHIDs === null) {168throw new Exception(169pht(170'You must use "withSourcePHIDs()" to query edges.'));171}172173$sources = phid_group_by_type($this->sourcePHIDs);174175$result = array();176177// When a query specifies types, make sure we return data for all queried178// types.179if ($this->edgeTypes) {180foreach ($this->sourcePHIDs as $phid) {181foreach ($this->edgeTypes as $type) {182$result[$phid][$type] = array();183}184}185}186187foreach ($sources as $type => $phids) {188$conn_r = PhabricatorEdgeConfig::establishConnection($type, 'r');189190$where = $this->buildWhereClause($conn_r);191$order = $this->buildOrderClause($conn_r);192193$edges = queryfx_all(194$conn_r,195'SELECT edge.* FROM %T edge %Q %Q',196PhabricatorEdgeConfig::TABLE_NAME_EDGE,197$where,198$order);199200if ($this->needEdgeData) {201$data_ids = array_filter(ipull($edges, 'dataID'));202$data_map = array();203if ($data_ids) {204$data_rows = queryfx_all(205$conn_r,206'SELECT edgedata.* FROM %T edgedata WHERE id IN (%Ld)',207PhabricatorEdgeConfig::TABLE_NAME_EDGEDATA,208$data_ids);209foreach ($data_rows as $row) {210$data_map[$row['id']] = idx(211phutil_json_decode($row['data']),212'data');213}214}215foreach ($edges as $key => $edge) {216$edges[$key]['data'] = idx($data_map, $edge['dataID'], array());217}218}219220foreach ($edges as $edge) {221$result[$edge['src']][$edge['type']][$edge['dst']] = $edge;222}223}224225$this->resultSet = $result;226return $result;227}228229230/**231* Convenience function for selecting edge destination PHIDs after calling232* execute().233*234* Returns a flat list of PHIDs matching the provided source PHID and type235* filters. By default, the filters are empty so all PHIDs will be returned.236* For example, if you're doing a batch query from several sources, you might237* write code like this:238*239* $query = new PhabricatorEdgeQuery();240* $query->setViewer($viewer);241* $query->withSourcePHIDs(mpull($objects, 'getPHID'));242* $query->withEdgeTypes(array($some_type));243* $query->execute();244*245* // Gets all of the destinations.246* $all_phids = $query->getDestinationPHIDs();247* $handles = id(new PhabricatorHandleQuery())248* ->setViewer($viewer)249* ->withPHIDs($all_phids)250* ->execute();251*252* foreach ($objects as $object) {253* // Get all of the destinations for the given object.254* $dst_phids = $query->getDestinationPHIDs(array($object->getPHID()));255* $object->attachHandles(array_select_keys($handles, $dst_phids));256* }257*258* @param list? List of PHIDs to select, or empty to select all.259* @param list? List of edge types to select, or empty to select all.260* @return list<phid> List of matching destination PHIDs.261*/262public function getDestinationPHIDs(263array $src_phids = array(),264array $types = array()) {265if ($this->resultSet === null) {266throw new PhutilInvalidStateException('execute');267}268269$result_phids = array();270271$set = $this->resultSet;272if ($src_phids) {273$set = array_select_keys($set, $src_phids);274}275276foreach ($set as $src => $edges_by_type) {277if ($types) {278$edges_by_type = array_select_keys($edges_by_type, $types);279}280281foreach ($edges_by_type as $edges) {282foreach ($edges as $edge_phid => $edge) {283$result_phids[$edge_phid] = true;284}285}286}287288return array_keys($result_phids);289}290291292/* -( Internals )---------------------------------------------------------- */293294295/**296* @task internal297*/298protected function buildWhereClause(AphrontDatabaseConnection $conn) {299$where = array();300301if ($this->sourcePHIDs) {302$where[] = qsprintf(303$conn,304'edge.src IN (%Ls)',305$this->sourcePHIDs);306}307308if ($this->edgeTypes) {309$where[] = qsprintf(310$conn,311'edge.type IN (%Ls)',312$this->edgeTypes);313}314315if ($this->destPHIDs) {316// potentially complain if $this->edgeType was not set317$where[] = qsprintf(318$conn,319'edge.dst IN (%Ls)',320$this->destPHIDs);321}322323return $this->formatWhereClause($conn, $where);324}325326327/**328* @task internal329*/330private function buildOrderClause(AphrontDatabaseConnection $conn) {331if ($this->order == self::ORDER_NEWEST_FIRST) {332return qsprintf($conn, 'ORDER BY edge.dateCreated DESC, edge.seq DESC');333} else {334return qsprintf($conn, 'ORDER BY edge.dateCreated ASC, edge.seq ASC');335}336}337338}339340341