1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-09-20 09:18:48 +02:00

Make edge types modular

Summary:
Ref T5245. I want to add a new capability to edge types, which is a good opportunity to move away from `PhabricatorEdgeConfig`, which isn't modular.

This is basically the same as the modularization of PHID types, which has worked well. Add `PhabricatorEdgeType` and provide an adaption layer for the existing code.

This has no runtime changes, except the fixed edge constant.

Test Plan: Ran `var_dump(PhabricatorEdgeType::getAllTypes())` and got reasonable looking output.

Reviewers: chad, btrahan, joshuaspence

Reviewed By: joshuaspence

Subscribers: epriestley

Maniphest Tasks: T5245

Differential Revision: https://secure.phabricator.com/D9837
This commit is contained in:
epriestley 2014-07-17 15:40:37 -07:00
parent 7deec8208f
commit 7afb770cbe
4 changed files with 402 additions and 1 deletions

View file

@ -1562,6 +1562,7 @@ phutil_register_library_map(array(
'PhabricatorEdgeGraph' => 'infrastructure/edges/util/PhabricatorEdgeGraph.php',
'PhabricatorEdgeQuery' => 'infrastructure/edges/query/PhabricatorEdgeQuery.php',
'PhabricatorEdgeTestCase' => 'infrastructure/edges/__tests__/PhabricatorEdgeTestCase.php',
'PhabricatorEdgeType' => 'infrastructure/edges/type/PhabricatorEdgeType.php',
'PhabricatorEditor' => 'infrastructure/PhabricatorEditor.php',
'PhabricatorEmailLoginController' => 'applications/auth/controller/PhabricatorEmailLoginController.php',
'PhabricatorEmailVerificationController' => 'applications/auth/controller/PhabricatorEmailVerificationController.php',
@ -1712,6 +1713,7 @@ phutil_register_library_map(array(
'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/PhabricatorJavelinLinter.php',
'PhabricatorJumpNavHandler' => 'applications/search/engine/PhabricatorJumpNavHandler.php',
'PhabricatorKeyValueDatabaseCache' => 'applications/cache/PhabricatorKeyValueDatabaseCache.php',
'PhabricatorLegacyEdgeType' => 'infrastructure/edges/type/PhabricatorLegacyEdgeType.php',
'PhabricatorLegalpadConfigOptions' => 'applications/legalpad/config/PhabricatorLegalpadConfigOptions.php',
'PhabricatorLegalpadPHIDTypeDocument' => 'applications/legalpad/phid/PhabricatorLegalpadPHIDTypeDocument.php',
'PhabricatorLipsumArtist' => 'applications/lipsum/image/PhabricatorLipsumArtist.php',
@ -4356,6 +4358,7 @@ phutil_register_library_map(array(
'PhabricatorEdgeGraph' => 'AbstractDirectedGraph',
'PhabricatorEdgeQuery' => 'PhabricatorQuery',
'PhabricatorEdgeTestCase' => 'PhabricatorTestCase',
'PhabricatorEdgeType' => 'Phobject',
'PhabricatorEditor' => 'Phobject',
'PhabricatorEmailLoginController' => 'PhabricatorAuthController',
'PhabricatorEmailVerificationController' => 'PhabricatorAuthController',
@ -4509,6 +4512,7 @@ phutil_register_library_map(array(
'PhabricatorIteratedMD5PasswordHasher' => 'PhabricatorPasswordHasher',
'PhabricatorJavelinLinter' => 'ArcanistLinter',
'PhabricatorKeyValueDatabaseCache' => 'PhutilKeyValueCache',
'PhabricatorLegacyEdgeType' => 'PhabricatorEdgeType',
'PhabricatorLegalpadConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorLegalpadPHIDTypeDocument' => 'PhabricatorPHIDType',
'PhabricatorLipsumGenerateWorkflow' => 'PhabricatorLipsumManagementWorkflow',

View file

@ -78,6 +78,13 @@ final class PhabricatorEdgeConfig extends PhabricatorEdgeConstants {
const TYPE_OBJECT_NEEDS_SIGNATURE = 49;
const TYPE_SIGNATURE_NEEDED_BY_OBJECT = 50;
/* !!!! STOP !!!! STOP !!!! STOP !!!! STOP !!!! STOP !!!! STOP !!!! STOP !!!! */
// HEY! DO NOT ADD NEW CONSTANTS HERE!
// Instead, subclass PhabricatorEdgeType.
/* !!!! STOP !!!! STOP !!!! STOP !!!! STOP !!!! STOP !!!! STOP !!!! STOP !!!! */
const TYPE_TEST_NO_CYCLE = 9000;
const TYPE_PHOB_HAS_ASANATASK = 80001;
@ -89,6 +96,47 @@ final class PhabricatorEdgeConfig extends PhabricatorEdgeConstants {
const TYPE_PHOB_HAS_JIRAISSUE = 80004;
const TYPE_JIRAISSUE_HAS_PHOB = 80005;
/**
* Build @{class:PhabricatorLegacyEdgeType} objects for edges which have not
* yet been modernized. This allows code to act as though we've completed
* the edge type migration before we actually do all the work, by building
* these fake type objects.
*
* @param list<const> List of edge types that objects should not be built for.
* This is used to avoid constructing duplicate objects for edge constants
* which have migrated and already have a real object.
* @return list<PhabricatorLegacyEdgeType> Real-looking edge type objects for
* unmigrated edge types.
*/
public static function getLegacyTypes(array $exclude) {
$consts = array_merge(
range(1, 50),
array(9000),
range(80000, 80005));
$consts = array_diff($consts, $exclude);
$map = array();
foreach ($consts as $const) {
$prevent_cycles = self::shouldPreventCycles($const);
$inverse_constant = self::getInverse($const);
$map[$const] = id(new PhabricatorLegacyEdgeType())
->setEdgeConstant($const)
->setShouldPreventCycles($prevent_cycles)
->setInverseEdgeConstant($inverse_constant)
->setStrings(
array(
self::getAddStringForEdgeType($const),
self::getRemoveStringForEdgeType($const),
self::getEditStringForEdgeType($const),
self::getFeedStringForEdgeType($const),
));
}
return $map;
}
public static function getInverse($edge_type) {
static $map = array(
self::TYPE_TASK_HAS_COMMIT => self::TYPE_COMMIT_HAS_TASK,
@ -136,7 +184,7 @@ final class PhabricatorEdgeConfig extends PhabricatorEdgeConstants {
self::TYPE_DREV_HAS_COMMIT => self::TYPE_COMMIT_HAS_DREV,
self::TYPE_COMMIT_HAS_DREV => self::TYPE_DREV_HAS_COMMIT,
self::TYPE_OBJECT_HAS_CONTRIBUTOR => self::TYPE_SUBSCRIBED_TO_OBJECT,
self::TYPE_OBJECT_HAS_CONTRIBUTOR => self::TYPE_CONTRIBUTED_TO_OBJECT,
self::TYPE_CONTRIBUTED_TO_OBJECT => self::TYPE_OBJECT_HAS_CONTRIBUTOR,
self::TYPE_TASK_HAS_MOCK => self::TYPE_MOCK_HAS_TASK,

View file

@ -0,0 +1,230 @@
<?php
/**
* Defines an edge type.
*
* Edges are typed, directed connections between two objects. They are used to
* represent most simple relationships, like when a user is subscribed to an
* object or an object is a member of a project.
*
* @task load Loading Types
*/
abstract class PhabricatorEdgeType extends Phobject {
// TODO: Make this final after we remove PhabricatorLegacyEdgeType.
/* final */ public function getEdgeConstant() {
$class = new ReflectionClass($this);
$const = $class->getConstant('EDGECONST');
if ($const === false) {
throw new Exception(
pht(
'EdgeType class "%s" must define an EDGECONST property.',
get_class($this)));
}
if (!is_int($const) || ($const <= 0)) {
throw new Exception(
pht(
'EdgeType class "%s" has an invalid EDGECONST property. Edge '.
'constants must be positive integers.',
get_class($this)));
}
return $const;
}
public function getInverseEdgeConstant() {
return null;
}
public function shouldPreventCycles() {
return false;
}
public function getTransactionAddString(
$actor,
$add_count,
$add_edges) {
return pht(
'%s added %s edge(s): %s.',
$actor,
$add_count,
$add_edges);
}
public function getTransactionRemoveString(
$actor,
$rem_count,
$rem_edges) {
return pht(
'%s removed %s edge(s): %s.',
$actor,
$rem_count,
$rem_edges);
}
public function getTransactionEditString(
$actor,
$total_count,
$add_count,
$add_edges,
$rem_count,
$rem_edges) {
return pht(
'%s edited %s edge(s), added %s: %s; removed %s: %s.',
$actor,
$total_count,
$add_count,
$add_edges,
$rem_count,
$rem_edges);
}
public function getFeedAddString(
$actor,
$object,
$add_count,
$add_edges) {
return pht(
'%s added %s edge(s) to %s: %s.',
$actor,
$add_count,
$object,
$add_edges);
}
public function getFeedRemoveString(
$actor,
$object,
$rem_count,
$rem_edges) {
return pht(
'%s removed %s edge(s) from %s: %s.',
$actor,
$rem_count,
$object,
$rem_edges);
}
public function getFeedEditString(
$actor,
$object,
$total_count,
$add_count,
$add_edges,
$rem_count,
$rem_edges) {
return pht(
'%s edited %s edge(s) for %s, added %s: %s; removed %s: %s.',
$actor,
$total_count,
$object,
$add_count,
$add_edges,
$rem_count,
$rem_edges);
}
/* -( Loading Types )------------------------------------------------------ */
/**
* @task load
*/
public static function getAllTypes() {
static $type_map;
if ($type_map === null) {
$types = id(new PhutilSymbolLoader())
->setAncestorClass(__CLASS__)
->loadObjects();
$map = array();
// TODO: Remove this once everything is migrated.
$exclude = mpull($types, 'getEdgeConstant');
$map = PhabricatorEdgeConfig::getLegacyTypes($exclude);
unset($types['PhabricatorLegacyEdgeType']);
foreach ($types as $class => $type) {
$const = $type->getEdgeConstant();
if (isset($map[$const])) {
throw new Exception(
pht(
'Two edge types ("%s", "%s") share the same edge constant '.
'(%d). Each edge type must have a unique constant.',
$class,
get_class($map[$const]),
$const));
}
$map[$const] = $type;
}
// Check that all the inverse edge definitions actually make sense. If
// edge type A says B is its inverse, B must exist and say that A is its
// inverse.
foreach ($map as $const => $type) {
$inverse = $type->getInverseEdgeConstant();
if ($inverse === null) {
continue;
}
if (empty($map[$inverse])) {
throw new Exception(
pht(
'Edge type "%s" ("%d") defines an inverse type ("%d") which '.
'does not exist.',
get_class($type),
$const,
$inverse));
}
$inverse_inverse = $map[$inverse]->getInverseEdgeConstant();
if ($inverse_inverse !== $const) {
throw new Exception(
pht(
'Edge type "%s" ("%d") defines an inverse type ("%d"), but that '.
'inverse type defines a different type ("%d") as its '.
'inverse.',
get_class($type),
$const,
$inverse,
$inverse_inverse));
}
}
$type_map = $map;
}
return $type_map;
}
/**
* @task load
*/
public static function getByConstant($const) {
$type = idx(self::getAllTypes(), $const);
if (!$type) {
throw new Exception(
pht('Unknown edge constant "%s"!', $const));
}
return $type;
}
}

View file

@ -0,0 +1,119 @@
<?php
/**
* Supports legacy edges. Do not use or extend this class!
*
* TODO: Move all edge constants out of @{class:PhabricatorEdgeConfig}, then
* throw this away.
*/
final class PhabricatorLegacyEdgeType extends PhabricatorEdgeType {
private $edgeConstant;
private $inverseEdgeConstant;
private $shouldPreventCycles;
private $strings;
public function getEdgeConstant() {
return $this->edgeConstant;
}
public function getInverseEdgeConstant() {
return $this->inverseEdgeConstant;
}
public function shouldPreventCycles() {
return $this->shouldPreventCycles;
}
public function setEdgeConstant($edge_constant) {
$this->edgeConstant = $edge_constant;
return $this;
}
public function setInverseEdgeConstant($inverse_edge_constant) {
$this->inverseEdgeConstant = $inverse_edge_constant;
return $this;
}
public function setShouldPreventCycles($should_prevent_cycles) {
$this->shouldPreventCycles = $should_prevent_cycles;
return $this;
}
public function setStrings(array $strings) {
$this->strings = $strings;
return $this;
}
private function getString($idx, array $argv) {
array_unshift($argv, idx($this->strings, $idx, ''));
// TODO: Burn this class in a fire. Just hiding this from lint for now.
$pht_func = 'pht';
return call_user_func_array($pht_func, $argv);
}
public function getTransactionAddString(
$actor,
$add_count,
$add_edges) {
$args = func_get_args();
return $this->getString(0, $args);
}
public function getTransactionRemoveString(
$actor,
$rem_count,
$rem_edges) {
$args = func_get_args();
return $this->getString(1, $args);
}
public function getTransactionEditString(
$actor,
$total_count,
$add_count,
$add_edges,
$rem_count,
$rem_edges) {
$args = func_get_args();
return $this->getString(2, $args);
}
public function getFeedAddString(
$actor,
$object,
$add_count,
$add_edges) {
$args = func_get_args();
return $this->getString(3, $args);
}
public function getFeedRemoveString(
$actor,
$object,
$rem_count,
$rem_edges) {
$args = func_get_args();
return $this->getString(3, $args);
}
public function getFeedEditString(
$actor,
$object,
$total_count,
$add_count,
$add_edges,
$rem_count,
$rem_edges) {
$args = func_get_args();
return $this->getString(3, $args);
}
}