Skip to content

MC-30776: Improve Performance of SVC #28

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "magento/magento-semver",
"description": "Magento semantic version checker",
"version": "4.0.0",
"version": "5.0.0",
"license": [
"OSL-3.0",
"AFL-3.0"
Expand All @@ -13,7 +13,8 @@
"symfony/console": "~4.1.0||~4.4.0",
Copy link
Contributor

@roribio roribio Mar 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes have gone out as part of v4.0.0. Please rebase your feature branch on develop.

Thanks!

"tomzx/php-semver-checker": "^0.13.0",
"wikimedia/less.php": "~1.8.0",
"zendframework/zend-stdlib": "^3.2.1"
"zendframework/zend-stdlib": "^3.2.1",
"nikic/php-parser": "^3.1"
},
"require-dev": {
"phpunit/phpunit": "^6.5.0",
Expand Down
32 changes: 16 additions & 16 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

210 changes: 100 additions & 110 deletions src/ClassHierarchy/DependencyInspectionVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,31 @@
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Interface_ as InterfaceNode;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\PropertyProperty;
use PhpParser\Node\Stmt\Trait_ as TraitNode;
use PhpParser\Node\Stmt\TraitUse;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitorAbstract;

/**
* Implements a visitor for `class`, `interface` and `trait` nodes that generates a dependency graph.
*/
class DependencyInspectionVisitor extends NodeVisitorAbstract
{

/** @var DependencyGraph */
private $dependencyGraph;

/** @var NodeHelper */
private $nodeHelper;

/**
* @var Entity
* Holds current Entity. Stored so we can populate this entity in our dependency graph upon walking relevant child
* nodes.
*/
private $currentClassLike = null;

/**
* Constructor.
*
Expand All @@ -43,135 +53,115 @@ public function __construct(DependencyGraph $dependencyGraph, NodeHelper $nodeHe
}

/**
* @inheritDoc
* Logic to process current node. We aggressively halt walking the AST since this may contain many nodes
* If we are visiting a Classlike node, set currentClassLike so we can populate this entity in our dependency graph
* upon walking relevant child nodes like PropertyProperty and ClassMethod.
*
* Inspect nodes after all visitors have run since we need the fully qualified names of nodes.
*/
public function leaveNode(Node $node)
{
if ($node instanceof ClassNode) {
$this->addClassNode($node);
} elseif ($node instanceof InterfaceNode) {
$this->addInterfaceNode($node);
} elseif ($node instanceof TraitNode) {
$this->addTraitNode($node);
}
}

/**
* Getter for {@link DependencyInspectionVisitor::$dependencyGraph}.
* Subparse tree we want to traverse will be something like:
* Namespace -> ClassLike -> ClassMethod
* -> TraitUse
* -> PropertyProperty
*
* @return DependencyGraph
*/
public function getDependencyGraph(): DependencyGraph
{
return $this->dependencyGraph;
}

/**
* @param ClassNode $node
*
* @inheritdoc
*
* @param Node $node
* @return int tells NodeTraverser whether to continue traversing
*/
private function addClassNode(ClassNode $node)
public function enterNode(Node $node)
{
// name is not set for anonymous classes, therefore they cannot be part of the dependency graph
if ($node->isAnonymous()) {
return;
}

$className = (string)$node->namespacedName;
$class = $this->dependencyGraph->findOrCreateClass($className);

[$methodList, $propertyList] = $this->fetchStmtsNodes($node);
$class->setMethodList($methodList);
$class->setPropertyList($propertyList);
$class->setIsApi($this->nodeHelper->isApiNode($node));

if ($node->extends) {
$parentClassName = (string)$node->extends;
$parentClassEntity = $this->dependencyGraph->findOrCreateClass($parentClassName);
$class->addExtends($parentClassEntity);
}

foreach ($node->implements as $implement) {
$interfaceName = (string)$implement;
$interfaceEntity = $this->dependencyGraph->findOrCreateInterface($interfaceName);
$class->addImplements($interfaceEntity);
}

foreach ($this->nodeHelper->getTraitUses($node) as $traitUse) {
foreach ($traitUse->traits as $trait) {
$traitName = (string)$trait;
$traitEntity = $this->dependencyGraph->findOrCreateTrait($traitName);
$class->addUses($traitEntity);
}
switch (true) {
case $node instanceof Node\Stmt\Namespace_:
return null;
case $node instanceof ClassLike:
//set currentClassLike entity
return $this->handleClassLike($node);
case $node instanceof ClassMethod:
$this->currentClassLike->addMethod($node);
return NodeTraverser::DONT_TRAVERSE_CHILDREN;
case $node instanceof TraitUse:
foreach ($node->traits as $trait) {
$traitName = (string)$trait;
$traitEntity = $this->dependencyGraph->findOrCreateTrait($traitName);
$this->currentClassLike->addUses($traitEntity);
}
return NodeTraverser::DONT_TRAVERSE_CHILDREN;
case $node instanceof PropertyProperty:
$this->currentClassLike->addProperty($node);
return NodeTraverser::DONT_TRAVERSE_CHILDREN;
default:
return NodeTraverser::DONT_TRAVERSE_CHILDREN;
}

$this->dependencyGraph->addEntity($class);
}

/**
* @param InterfaceNode $node
* Handles Class, Interface, and Traits nodes. Sets currentClassLike entity and will populate extends, implements,
* and API information
*
* @param ClassLike $node
* @return int|null
*/
private function addInterfaceNode(InterfaceNode $node)
private function handleClassLike(ClassLike $node)
{
$interfaceName = (string)$node->namespacedName;
$interface = $this->dependencyGraph->findOrCreateInterface($interfaceName);

$interface->setIsApi($this->nodeHelper->isApiNode($node));
[$methodList] = $this->fetchStmtsNodes($node);
$interface->setMethodList($methodList);

foreach ($node->extends as $extend) {
$interfaceName = (string)$extend;
$interfaceEntity = $this->dependencyGraph->findOrCreateInterface($interfaceName);
$interface->addExtends($interfaceEntity);
/**
* @var \PhpParser\Node\Name $namespacedName
* This is set in the NamespaceResolver visitor
*/
$namespacedName = $node->namespacedName;
switch (true) {
case $node instanceof ClassNode:
if ($node->isAnonymous()) {
return NodeTraverser::STOP_TRAVERSAL;
}
$this->currentClassLike = $this->dependencyGraph->findOrCreateClass((string)$namespacedName);
if ($node->extends) {
$parentClassName = (string)$node->extends;
$parentClassEntity = $this->dependencyGraph->findOrCreateClass($parentClassName);
$this->currentClassLike->addExtends($parentClassEntity);
}
foreach ($node->implements as $implement) {
$interfaceName = (string)$implement;
$interfaceEntity = $this->dependencyGraph->findOrCreateInterface($interfaceName);
$this->currentClassLike->addImplements($interfaceEntity);
}
break;
case $node instanceof InterfaceNode:
$this->currentClassLike = $this->dependencyGraph->findOrCreateInterface((string)$namespacedName);
foreach ($node->extends as $extend) {
$interfaceName = (string)$extend;
$interfaceEntity = $this->dependencyGraph->findOrCreateInterface($interfaceName);
$this->currentClassLike->addExtends($interfaceEntity);
}
break;
case $node instanceof TraitNode:
$this->currentClassLike = $this->dependencyGraph->findOrCreateTrait((string)$namespacedName);
break;
}

$this->dependencyGraph->addEntity($interface);
$this->currentClassLike->setIsApi($this->nodeHelper->isApiNode($node));
return null;
}

/**
* @param TraitNode $node
/*
* Unsets currentClassLike upon exiting ClassLike node. This is for cleanup, although this is not necessary since
* Classmethod, PropertyProperty, and TraitUse nodes will only be traversed after Classlike
*
* @param Node $node
* @return false|int|Node|Node[]|void|null
*/
private function addTraitNode(TraitNode $node)
public function leaveNode(Node $node)
{
$traitName = (string)$node->namespacedName;
$trait = $this->dependencyGraph->findOrCreateTrait($traitName);

[$methodList, $propertyList] = $this->fetchStmtsNodes($node);
$trait->setMethodList($methodList);
$trait->setPropertyList($propertyList);
$trait->setIsApi($this->nodeHelper->isApiNode($node));

foreach ($this->nodeHelper->getTraitUses($node) as $traitUse) {
foreach ($traitUse->traits as $parentTrait) {
$parentTraitName = (string)$parentTrait;
$parentTraitEntity = $this->dependencyGraph->findOrCreateTrait($parentTraitName);
$trait->addUses($parentTraitEntity);
}
if ($node instanceof ClassLike) {
$this->currentClassLike = null;
}

$this->dependencyGraph->addEntity($trait);
}

/**
* @param ClassLike $node
* @return array
* Getter for {@link DependencyInspectionVisitor::$dependencyGraph}.
*
* @return DependencyGraph
*/
private function fetchStmtsNodes(ClassLike $node): array
public function getDependencyGraph(): DependencyGraph
{
$methodList = [];
$propertyList = [];
foreach ($node->stmts as $stmt) {
if ($stmt instanceof ClassMethod) {
$methodList[$stmt->name] = $stmt;
} elseif ($stmt instanceof Property) {
foreach ($stmt->props as $prop) {
$propertyList[$prop->name] = $prop;
}
}
}

return [$methodList, $propertyList];
return $this->dependencyGraph;
}
}
Loading