Skip to content
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

Add general purpose Attributes, replace Layoutable #103

Merged
merged 7 commits into from
Dec 31, 2014
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
4 changes: 2 additions & 2 deletions lib/Fhaculty/Graph/Algorithm/MaximumMatching/Flow.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ public function getEdges()
// create temporary flow graph with supersource and supersink
$graphFlow = $this->graph->createGraphCloneEdgeless();

$superSource = $graphFlow->createVertex()->setLayoutAttribute('label', 's*');
$superSink = $graphFlow->createVertex()->setLayoutAttribute('label', 't*');
$superSource = $graphFlow->createVertex();
$superSink = $graphFlow->createVertex();

$groups = $alg->getGroups();
$groupA = $groups[0];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ public function createGraph()
// create resulting graph with supersource and supersink
$resultGraph = $this->graph->createGraphClone();

$superSource = $resultGraph->createVertex()->setLayoutAttribute('label', 's*');
$superSink = $resultGraph->createVertex()->setLayoutAttribute('label', 't*');
$superSource = $resultGraph->createVertex();
$superSink = $resultGraph->createVertex();

$sumBalance = 0;

Expand Down
35 changes: 35 additions & 0 deletions lib/Fhaculty/Graph/Attribute/AttributeAware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace Fhaculty\Graph\Attribute;

/**
* Implemented by any entity that is aware of additional attributes
*
* Each attribute consists of a name (string) and an arbitrary value.
*/
interface AttributeAware
{
/**
* get a single attribute with the given $name (or return $default if attribute was not found)
*
* @param string $name
* @param mixed $default to return if attribute was not found
* @return mixed
*/
public function getAttribute($name, $default = null);

/**
* set a single attribute with the given $name to given $value
*
* @param string $name
* @param mixed $value
*/
public function setAttribute($name, $value);

/**
* get a container for all attributes
*
* @return AttributeBag
*/
public function getAttributeBag();
}
27 changes: 27 additions & 0 deletions lib/Fhaculty/Graph/Attribute/AttributeBag.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace Fhaculty\Graph\Attribute;

/**
* Interface to container that represents multiple attributes
*/
interface AttributeBag extends AttributeAware
{
// public function getAttribute($name, $default);
// public function setAttribute($name, $value);
// public function getAttributeBag();

/**
* set an array of additional attributes
*
* @param array $attributes
*/
public function setAttributes(array $attributes);

/**
* get an array of all attributes
*
* @return array
*/
public function getAttributes();
}
76 changes: 76 additions & 0 deletions lib/Fhaculty/Graph/Attribute/AttributeBagContainer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

namespace Fhaculty\Graph\Attribute;

/**
* A fairly standard AttributeBag container.
*
* This container passes and returns attributes by value. It is mutable,
* however, so multiple references to the container will update in kind.
*/
class AttributeBagContainer implements AttributeBag
{
/**
* @var array
*/
private $attributes = array();

/**
* get a single attribute with the given $name (or return $default if attribute was not found)
*
* @param string $name
* @param mixed $default to return if attribute was not found
* @return mixed
*/
public function getAttribute($name, $default = null)
{
return isset($this->attributes[$name]) ? $this->attributes[$name] : $default;
}

/**
* set a single attribute with the given $name to given $value
*
* @param string $name
* @param mixed $value
* @return self For a fluid interface.
*/
public function setAttribute($name, $value)
{
$this->attributes[$name] = $value;

return $this;
}

/**
* get an array of all attributes
*
* @return array
*/
public function getAttributes()
{
return $this->attributes;
}

/**
* set an array of additional attributes
*
* @param array $attributes
* @return self For a fluid interface.
*/
public function setAttributes(array $attributes)
{
$this->attributes = $attributes + $this->attributes;

return $this;
}

/**
* get a container for all attributes
*
* @return AttributeBag
*/
public function getAttributeBag()
{
return $this;
}
}
116 changes: 116 additions & 0 deletions lib/Fhaculty/Graph/Attribute/AttributeBagNamespaced.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php

namespace Fhaculty\Graph\Attribute;

/**
* An attribute bag that automatically prefixes a given namespace.
*
* For example, you can use this class to prefix the attributes using a vendor
* name, like "myvendor.item.". If another vendor shares the base attribute
* bag, it can use a different prefix, like "otherProduct.item.". This allows
* both libraries to have attributes with the same name without having them
* conflict. For example, the attribute "id" would be stored separately as
* "myvendor.item.id" and "otherProduct.item.id".
*/
class AttributeBagNamespaced implements AttributeBag
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice idea!

{
/**
* @var AttributeBag
*/
private $bag;

/**
* @var string
*/
private $prefix;

/**
* Initialize the attribute bag with a prefix to use as a namespace for the attributes.
*
* @param AttributeAware $bag The bag to store the prefixed attributes in.
* @param string $prefix The prefix to prepend to all attributes before
* storage. This prefix acts as a namespace to separate attributes.
*/
public function __construct(AttributeAware $bag, $prefix)
{
if (!($bag instanceof AttributeBag)) {
$bag = $bag->getAttributeBag();
}
$this->bag = $bag;
$this->prefix = $prefix;
}

/**
* get a single attribute with the given $name (or return $default if attribute was not found)
*
* This prefixes the attribute name before requesting from the base bag.
*
* @param string $name
* @param mixed $default to return if attribute was not found
* @return mixed
*/
public function getAttribute($name, $default = null)
{
return $this->bag->getAttribute($this->prefix . $name, $default);
}

/**
* set a single attribute with the given $name to given $value
*
* This prefixes the attribute name before setting in the base bag.
*
* @param string $name
* @param mixed $value
* @return void
*/
public function setAttribute($name, $value)
Copy link
Contributor

Choose a reason for hiding this comment

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

The set methods in this class don't return $this for the fluent interface like the other implementations of AttributeBag do. Is that on purpose? Should the fluent interface be part of the AttributeAware and AttributeBag interfaces?

{
$this->bag->setAttribute($this->prefix . $name, $value);
}

/**
* get an array of all attributes
*
* The prefix will not be included in the returned attribute keys.
*
* @return array
*/
public function getAttributes()
{
$attributes = array();
$len = strlen($this->prefix);

foreach ($this->bag->getAttributes() as $name => $value) {
if (strpos($name, $this->prefix) === 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Couldn't this unintentionally remove a prefix if the prefix is purposefully included multiple times using nested attribute bags? For example, commit.commit.sha.

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't see how this could happen? Care to elaborate or perhaps even provide a test case? :)

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe you are correct. I didn't quite grasp the implementation here the first time through, but it looks correct even in weird cases like:

$bag = new AttributeBagContainer();
$commitBag = new AttributeBagNamespaced($bag, 'commit.');
$commitDetailBag = new AttributeBagNamespaced($commitBag, 'commit.');
// ...

I do believe that something like this could cause problems. They aren't critical problems, but they do allow people to get around the namespacing:

$bag = new AttributeBagContainer();
$bag->setAttribute('commit.sha', 'abc123');

$commitBag = new AttributeBagNamespaced($bag, 'commit.');
$commitBag->getAttribute('sha'); // 'abc123'

$attributes[substr($name, $len)] = $value;
}
}

return $attributes;
}

/**
* set an array of additional attributes
*
* Each attribute is prefixed before setting in the base bag.
*
* @param array $attributes
* @return void
*/
public function setAttributes(array $attributes)
{
foreach ($attributes as $name => $value) {
$this->bag->setAttribute($this->prefix . $name, $value);
}
}

/**
* get a container for all attributes
*
* @return AttributeBag
*/
public function getAttributeBag()
{
return $this;
}
}
90 changes: 90 additions & 0 deletions lib/Fhaculty/Graph/Attribute/AttributeBagReference.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

namespace Fhaculty\Graph\Attribute;

/**
* The basic attribute bag, but using a reference to the base attribute array.
*
* This container passes and returns attributes by value, but stores them in a
* pass-by-reference array. It is mutable, however, so multiple references to
* the container will update in kind.
*/
class AttributeBagReference implements AttributeBag
{
/**
* @var array
*/
private $attributes;

/**
* Initialize the attribute bag with the base attribute array.
*
* The given array is pass-by-reference, so updates to the array here or in
* calling code will be reflected everywhere.
*
* @param array $attributes The pass-by-reference attributes.
*/
public function __construct(array &$attributes)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this by ref. This will be confusing. Say

$ar['color'] = 'red';
$a_attr = new AttributeBagReference($a);
$ar['color'] = 'blue';
$b_attr = new AttributeBagReference($a);

if ($a_attr->getAttribute('color') == $b_attr->getAttribute('color'))
{
    echo "Weird";
}

Or did I miss something?

Copy link
Member Author

Choose a reason for hiding this comment

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

The AttributeBagRef is designed to be a lightweight temporary instance so that we can expose the attributes through the AttributeBag interface without actually storing the instance. I.e. every time you call $vertex->getAttributeBag() you get another instance (which is just an implementation detail), but you can still access all properties nonetheless.

The AttributeBagContainer has it's own internal set of attributes and could be used as an alternative to the AttributeBagRef. Using it instead would require every Vertex to have (or at least lazy-load) a dedicated instance and then implement it like this:

class Vertex
{
    public function getAttribute($name)
    {
        return $this->getAttributeBag()->getAttribute($name);
    }
}

I suspected this to be inefficient (additional instances, more references, forwarded method calls etc.), but we should probably run some benchmarks first.

{
$this->attributes =& $attributes;
}

/**
* get a single attribute with the given $name (or return $default if attribute was not found)
*
* @param string $name
* @param mixed $default to return if attribute was not found
* @return mixed
*/
public function getAttribute($name, $default = null)
{
return isset($this->attributes[$name]) ? $this->attributes[$name] : $default;
}

/**
* set a single attribute with the given $name to given $value
*
* @param string $name
* @param mixed $value
* @return self For a fluid interface.
*/
public function setAttribute($name, $value)
{
$this->attributes[$name] = $value;

return $this;
}

/**
* get an array of all attributes
*
* @return array
*/
public function getAttributes()
{
return $this->attributes;
}

/**
* set an array of additional attributes
*
* @param array $attributes
* @return self For a fluid interface.
*/
public function setAttributes(array $attributes)
{
$this->attributes = $attributes + $this->attributes;

return $this;
}

/**
* get a container for all attributes
*
* @return AttributeBag
*/
public function getAttributeBag()
{
return $this;
}
}
Loading