Created
December 29, 2011 17:04
-
-
Save m4rw3r/1534999 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
* Generated by RapidDataMapper on 2011-11-02 13:09:36. | |
* | |
* Copyright (c) 2010 Martin Wernståhl. | |
* All rights reserved. | |
*/ | |
class ArtistCollectionBase extends Rdm_Collection | |
{ | |
// Relation id constants | |
/** | |
* Constant representing the relation tracks between Artist and \Track. | |
*/ | |
const Tracks = 216046; | |
/** | |
* Constant representing the relation albums between Artist and \Album. | |
*/ | |
const Albums = 254190; | |
protected static $unit_of_work = null; | |
protected static $refresh = false; | |
/** | |
* Internal: The object which manages this collection class. | |
* | |
* @var Rdm_CollectionManager | |
*/ | |
public static $manager; | |
/** | |
* Internal: The database connection. | |
* | |
* @var Rdm_Adapter | |
*/ | |
public static $db; | |
public $table_alias = 'artist'; | |
/** | |
* Registers a CollectionManager for this collection. | |
* | |
* @param Rdm_CollectionManager | |
* @return void | |
*/ | |
public static function setCollectionManager(Rdm_CollectionManager $manager) | |
{ | |
self::$manager = $manager; | |
self::$db = $manager->getConfig()->getAdapter(); | |
$manager->registerCollectionClassName('ArtistCollection', array()); | |
$u = new ArtistUnitOfWork; | |
$u->setAdapter(self::$db); | |
$u->setChangeTrackingPolicy(Rdm_UnitOfWork::IMPLICIT); | |
self::$unit_of_work = $u; | |
} | |
/** | |
* Pushes all changes to the database. | |
* | |
* @param boolean If to only push changes for the ArtistCollection | |
* @return void | |
*/ | |
public static function pushChanges($private_push = false) | |
{ | |
if( ! $private_push) | |
{ | |
return self::$manager->pushChanges(); | |
} | |
else | |
{ | |
return self::$unit_of_work->commit(); | |
} | |
} | |
public static function getUnitOfWork() | |
{ | |
return self::$unit_of_work; | |
} | |
/** | |
* Internal: Returns the UnitOfWork instance for this collection, | |
* PHP 5.2 compatibility. | |
* | |
* @return Rdm_UnitOfWork | |
*/ | |
public function getUnitOfWorkInstance() | |
{ | |
return self::$unit_of_work; | |
} | |
/** | |
* @return ArtistCollection | |
*/ | |
public static function create() | |
{ | |
return new ArtistCollection; | |
} | |
/** | |
* Returns a new \ArtistCollection instance which filters its entities by the supplied \Track entity. | |
* | |
* @param \Track | |
* @return ArtistCollection | |
*/ | |
public static function createFromTracks(\Track $related_entity) | |
{ | |
$c = new ArtistCollection; | |
$c->has()->relatedTracks($related_entity); | |
return $c; | |
} | |
/** | |
* Returns a new \ArtistCollection instance which filters its entities by the supplied \Album entity. | |
* | |
* @param \Album | |
* @return ArtistCollection | |
*/ | |
public static function createFromAlbums(\Album $related_entity) | |
{ | |
$c = new ArtistCollection; | |
$c->has()->relatedAlbums($related_entity); | |
return $c; | |
} | |
/** | |
* Persists a newly created \Artist object in the database. | |
* | |
* @param Artist | |
* @return void | |
*/ | |
public static function persist($object) | |
{ | |
if( ! $object instanceof Artist) | |
{ | |
throw Rdm_Collection_Exception::expectingObjectOfClass('\Artist'); | |
} | |
self::$unit_of_work->addNewEntity($object); | |
} | |
/** | |
* Deletes an object of type Artist from the database. | |
* | |
* @param Artist | |
* @return void | |
*/ | |
public static function delete($object) | |
{ | |
if( ! $object instanceof Artist) | |
{ | |
throw Rdm_Collection_Exception::expectingObjectOfType('Artist'); | |
} | |
self::$unit_of_work->addForDelete($object, implode('|', $object->__id)); | |
} | |
/** | |
* Fetches a single object from the database based on its primary key. | |
* | |
* @param string id | |
* @return Artist|false | |
*/ | |
public static function fetchByPrimaryKey($id) | |
{ | |
$c = new ArtistCollection; | |
$c->filters[] = '`artist`.`id` = '.self::$db->escape($id); | |
$c->populate(); | |
return current($c->contents); | |
} | |
/** | |
* Fetches a single object from the database based on its name value. | |
* | |
* @param string name | |
* @return Artist|false | |
*/ | |
public static function fetchByName($name) | |
{ | |
$c = new ArtistCollection; | |
$c->filters[] = '`artist`.`name` = '.self::$db->escape($name); | |
$c->populate(); | |
return current($c->contents); | |
} | |
/** | |
* Fetches a single object from the database based on its id value. | |
* | |
* @param string id | |
* @return Artist|false | |
*/ | |
public static function fetchById($id) | |
{ | |
$c = new ArtistCollection; | |
$c->filters[] = '`artist`.`id` = '.self::$db->escape($id); | |
$c->populate(); | |
return current($c->contents); | |
} | |
/** | |
* Fetches a single object from the database based on its related tracks object. | |
* | |
* @param \Track | |
* @return Artist|false | |
*/ | |
public static function fetchByRelatedTracks(\Track $tracks) | |
{ | |
$c = new ArtistCollection; | |
$c->has()->relatedTracks($tracks); | |
$c->limit(1); | |
$c->populate(); | |
return current($c->contents); | |
} | |
/** | |
* Fetches a single object from the database based on its related albums object. | |
* | |
* @param \Album | |
* @return Artist|false | |
*/ | |
public static function fetchByRelatedAlbums(\Album $albums) | |
{ | |
$c = new ArtistCollection; | |
$c->has()->relatedAlbums($albums); | |
$c->limit(1); | |
$c->populate(); | |
return current($c->contents); | |
} | |
/** | |
* Returns the adapter which is registered with this collection, | |
* used because of PHP 5.2 compatibility. | |
* | |
* @return Rdm_Adapter | |
*/ | |
public function getAdapter() | |
{ | |
return self::$db; | |
} | |
/** | |
* Joins this collection's results with related data from one of this collection's | |
* relations. | |
* | |
* @param int A relation constant, one of the following: Tracks, Albums | |
* @return \TrackCollection|\AlbumCollection | |
*/ | |
public function with($relation_id) | |
{ | |
if($relation_id == ArtistCollection::Tracks) | |
{ | |
if(isset($this->with['tracks'])) | |
{ | |
return $this->with['tracks']; | |
} | |
// Create the nested collection, and nested alias is the relation name if it isn't a nested join | |
$c = new \TrackCollection($this, new ArtistTracksRelation(), empty($this->parent) ? 'tracks' : $this->table_alias.'_tracks'); | |
$this->with['tracks'] = $c; | |
return $c; | |
} | |
elseif($relation_id == ArtistCollection::Albums) | |
{ | |
if(isset($this->with['albums'])) | |
{ | |
return $this->with['albums']; | |
} | |
// Create the nested collection, and nested alias is the relation name if it isn't a nested join | |
$c = new \AlbumCollection($this, new ArtistAlbumsRelation(), empty($this->parent) ? 'albums' : $this->table_alias.'_albums'); | |
$this->with['albums'] = $c; | |
return $c; | |
} | |
throw new Exception('No matching relation can be found for the class "Artist"'); | |
} | |
public function createSelectPart(&$list, &$column_mappings) | |
{ | |
$list[] = $this->table_alias.'.`id`, '.$this->table_alias.'.`name`'; | |
$column_mappings[] = $this->table_alias.'.id'; | |
$column_mappings[] = $this->table_alias.'.name'; | |
foreach($this->with as $join_alias => $join) | |
{ | |
$join->createSelectPart($list, $column_mappings); | |
} | |
} | |
public function createSelectCountPart($from) | |
{ | |
return 'SELECT COUNT(DISTINCT '.$this->table_alias.'.`id`) '.$from; | |
} | |
public function createFromPart($parent_alias, &$list) | |
{ | |
if( ! $parent_alias) | |
{ | |
// We're the root node, use FROM | |
$list[] = 'FROM `artists` AS `artist`'; | |
} | |
else | |
{ | |
// If we have conditions for a JOINed collection, then we use INNER JOIN | |
$type = isset($this->filters[0]) ? 'INNER ' : 'LEFT '; | |
// Set the aliases for the relation filter | |
$this->relation->setAliases($this->table_alias, $parent_alias); | |
$list[] = $type.'JOIN `artists` AS '.$this->table_alias.' ON '.implode(' ', $this->filters); | |
} | |
foreach($this->with as $join_alias => $join) | |
{ | |
$join->createFromPart($this->table_alias, $list); | |
} | |
} | |
public function hydrateObject(&$row, &$result, &$map) | |
{ | |
$id = $row[$map[$this->table_alias.'.id']]; | |
if(empty($id)) | |
{ | |
return false; | |
} | |
if( ! isset($result[$id])) | |
{ | |
$refresh = self::$refresh; | |
if(isset(self::$unit_of_work->entities[$id])) | |
{ | |
$e = self::$unit_of_work->entities[$id]; | |
} | |
else | |
{ | |
// Create a new instance | |
$e = new Artist; | |
self::$unit_of_work->entities[$id] = $e; | |
$refresh = true; | |
} | |
if($refresh) | |
{ | |
$e->__id = array('id' => (Int)$row[$map[$this->table_alias.'.id']]); | |
$e->id = $e->__id['id']; | |
$e->name = $row[$map[$this->table_alias.'.name']]; | |
$e->__data = array('name' => $e->name); | |
} | |
$result[$id] = $e; | |
} | |
else | |
{ | |
$e = $result[$id]; | |
} | |
foreach($this->with as $join_alias => $join) | |
{ | |
if($join->join_type == Rdm_Descriptor::HAS_MANY OR $join->join_type == Rdm_Descriptor::MANY_TO_MANY) | |
{ | |
// Check if there is a collection and that it is properly linked | |
if(empty($e->$join_alias) OR ! $e->$join_alias instanceof Rdm_Collection OR ! $e->$join_alias->created_by_hydrate) | |
{ | |
$e->$join_alias = clone $join; | |
$e->$join_alias->relation->parent_object = $e; | |
$e->$join_alias->setPopulated(); | |
$e->$join_alias->created_by_hydrate = true; | |
} | |
$join->hydrateObject($row, $e->$join_alias->contents, $map); | |
} | |
else | |
{ | |
$hash = array(); | |
$join->hydrateObject($row, $hash, $map); | |
if($hash) | |
{ | |
$e->$join_alias = array_shift($hash); | |
} | |
} | |
} | |
} | |
public function createFilterInstance() | |
{ | |
return new ArtistCollectionFilter($this, self::$db, $this->table_alias); | |
} | |
/** | |
* Converts the supplied entity to an XML fragment. | |
* | |
* Format: | |
* <code> | |
* <singular> | |
* <property>value</property> | |
* </singular> | |
* </code> | |
* | |
* @param Artist | |
* @return string | |
*/ | |
public static function entityToXML($object) | |
{ | |
return '<artist> | |
<id>'.htmlspecialchars($object->id).'</id> | |
<name>'.htmlspecialchars($object->name).'</name> | |
</artist>'; | |
} | |
/** | |
* Adds an \Artist entity to this collection, this collection will be locked and | |
* the entity will assume data which matches the filters of this collection. | |
* | |
* @param \Artist | |
* @return self | |
*/ | |
public function add($object) | |
{ | |
if( ! $object instanceof Artist) | |
{ | |
throw Rdm_Collection_Exception::expectingObjectOfClass('\Artist'); | |
} | |
// Modify the object | |
if($this->_add($object)) | |
{ | |
// Already in this collection | |
return $this; | |
} | |
// Add it to this collection's data | |
if( ! empty($object->__id)) | |
{ | |
$this->contents[implode('|', $object->__id)] = $object; | |
} | |
else | |
{ | |
ArtistCollection::persist($object); | |
$this->contents[] = $object; | |
} | |
return $this; | |
} | |
/** | |
* Removes an \Artist entity from this collection, this collection will be locked and | |
* the entity will TODO | |
* | |
* @param \Artist | |
* @return self | |
*/ | |
public function remove($object) | |
{ | |
if( ! $object instanceof Artist) | |
{ | |
throw Rdm_Collection_Exception::expectingObjectOfClass('\Artist'); | |
} | |
// Modify the object | |
$this->_remove($object); | |
return $this; | |
} | |
public function deleteAll() | |
{ | |
$this->is_locked = true; | |
$this->getUnitOfWorkInstance()->addCustomDelete(new Rdm_Collection_DeleteContentsOfCollectionOperation($this, '`artist`.`id`', 'DELETE FROM artists WHERE (`artists`.`id`) IN ')); | |
$c = $this->count(); | |
return $c; | |
} | |
} | |
if( ! class_exists('ArtistCollection')) | |
{ | |
class ArtistCollection extends ArtistCollectionBase | |
{ | |
// Intentionally left empty, this class can be replaced by a user class with the same name | |
} | |
} | |
class ArtistCollectionFilter extends Rdm_Collection_Filter | |
{ | |
public function idLt($id) | |
{ | |
empty($this->filters) OR $this->filters[] = 'AND'; | |
$this->filters[] = $this->table_alias.'.`id` < '.$this->db->escape($id); | |
$this->is_dynamic = true; | |
return $this; | |
} | |
public function idGt($id) | |
{ | |
empty($this->filters) OR $this->filters[] = 'AND'; | |
$this->filters[] = $this->table_alias.'.`id` > '.$this->db->escape($id); | |
$this->is_dynamic = true; | |
return $this; | |
} | |
public function id($id) | |
{ | |
empty($this->filters) OR $this->filters[] = 'AND'; | |
$this->filters[] = $this->table_alias.'.`id` = '.$this->db->escape($id); | |
return $this; | |
} | |
public function nameLt($name) | |
{ | |
empty($this->filters) OR $this->filters[] = 'AND'; | |
$this->filters[] = $this->table_alias.'.`name` < '.$this->db->escape($name); | |
$this->is_dynamic = true; | |
return $this; | |
} | |
public function nameGt($name) | |
{ | |
empty($this->filters) OR $this->filters[] = 'AND'; | |
$this->filters[] = $this->table_alias.'.`name` > '.$this->db->escape($name); | |
$this->is_dynamic = true; | |
return $this; | |
} | |
public function nameLike($name) | |
{ | |
empty($this->filters) OR $this->filters[] = 'AND'; | |
$this->filters[] = $this->table_alias.'.`name` LIKE '.$this->db->escape($name, true); | |
$this->is_dynamic = true; | |
return $this; | |
} | |
public function name($name) | |
{ | |
empty($this->filters) OR $this->filters[] = 'AND'; | |
$this->filters[] = $this->table_alias.'.`name` = '.$this->db->escape($name); | |
return $this; | |
} | |
public function relatedTracks(\Track $object) | |
{ | |
$this->modifiers[] = $o = new ArtistTracksRelation($object, $this->table_alias); | |
$this->filters[] = $o; | |
return $this; | |
} | |
public function relatedTracksCollection(\TrackCollection $object) | |
{ | |
$this->is_dynamic = true; | |
empty($this->filters) OR $this->filters[] = 'AND'; | |
$this->filters[] = '('.$this->table_alias.'.`id`) IN ('.$object->createSelectColumnsQuery($object->table_alias.'.`artist_id`').')'; | |
return $this; | |
} | |
public function relatedAlbums(\Album $object) | |
{ | |
$this->modifiers[] = $o = new ArtistAlbumsRelation($object, $this->table_alias); | |
$this->filters[] = $o; | |
return $this; | |
} | |
public function relatedAlbumsCollection(\AlbumCollection $object) | |
{ | |
$this->is_dynamic = true; | |
empty($this->filters) OR $this->filters[] = 'AND'; | |
$this->filters[] = '('.$this->table_alias.'.`id`) IN ('.$object->createSelectColumnsQuery($object->table_alias.'.`artist_id`').')'; | |
return $this; | |
} | |
} | |
class ArtistUnitOfWork extends Rdm_UnitOfWork | |
{ | |
protected function establishRelationLinks() | |
{ | |
} | |
protected function processSingleInserts() | |
{ | |
foreach($this->new_entities as $entity) | |
{ | |
$this->db->query('INSERT INTO `artists` (`name`) VALUES ('.$this->db->escape($entity->name).')'); | |
$entity->id = $entity->__id['id'] = $this->db->insertId(); | |
} | |
} | |
protected function processSingleChanges() | |
{ | |
$to_update = array(); | |
foreach($this->modified as $e) | |
{ | |
$data = array(); | |
$e->__data['name'] !== $e->name && $data[] = '`name` = '.$this->db->escape($e->name); | |
if( ! empty($data)) | |
{ | |
$this->db->query('UPDATE `artists` SET '.implode(', ', $data).' WHERE `id` = '.$this->db->escape($e->__id['id'])); | |
} | |
} | |
} | |
protected function processSingleDeletes() | |
{ | |
$ids = array(); | |
foreach($this->deleted_entities as $e) | |
{ | |
$ids[] = '('.implode(', ', array_map(array($this->db, 'escape'), $e->__id)).')'; | |
} | |
if( ! empty($ids)) | |
{ | |
$this->db->query('DELETE FROM `artists` WHERE (`id`) IN ('.implode(', ', $ids).')'); | |
} | |
} | |
protected function updateShadowData() | |
{ | |
foreach(array_merge($this->modified, $this->new_entities) as $e) | |
{ | |
$e->__data = array( | |
'name' => $e->name | |
); | |
} | |
} | |
} | |
/** | |
* A class which handles relations between Artist and tracks: it can produce join conditions | |
* for Artist to tracks, filter Artist by tracks and finally establish a relation between Artist and tracks. | |
*/ | |
class ArtistTracksRelation implements Rdm_Collection_FilterInterface | |
{ | |
public $id = ArtistCollection::Tracks; | |
public $type = 'relation.HAS_MANY'; | |
public $reverse = false; | |
public $parent_object; | |
public $alias; | |
public $parent_alias; | |
/** | |
* Internal: Creates a new Relation filter which filters Artist by their | |
* related tracks, this will put it in "reverse mode", if an $object is not present, "reverse mode" won't be triggered. | |
* | |
* So for relating from Artist to tracks, ie. JOIN tracks to | |
* the Artist query, use new ArtistTracksRelation; | |
* | |
* To filter Artist by an already fetched tracks object (\Track), | |
* ie. WHERE Artist.key = $related_object->otherkey, | |
* use new ArtistTracksRelation($related_object, 'alias for Artist'); | |
* | |
* @internal | |
* @param \Track|\Artist The instance to filter by, if the entities to fetch | |
* should relate to this specific instance, optional. | |
* If it is a \Track entity, it will relate to \Artist, and reverse if it is a \Artist. | |
* @param string The alias of the table containing Artist which should be filtered by | |
* $object. | |
*/ | |
public function __construct($object = null, $alias = '') | |
{ | |
if( ! is_null($object)) | |
{ | |
$this->parent_object = $object; | |
if($object instanceof \Track) | |
{ | |
// Relating from Track to Artist | |
$this->reverse = true; | |
} | |
} | |
$this->alias = $alias; | |
} | |
/** | |
* Internal: Sets the aliases to use by this relation filter to create the ON filter | |
* between the two tables. | |
* | |
* @internal | |
* @param string | |
* @param string | |
*/ | |
public function setAliases($alias, $parent_alias) | |
{ | |
$this->alias = $alias; | |
$this->parent_alias = $parent_alias; | |
} | |
/** | |
* Internal: Returns true if this relation filter can modify a related object. | |
* | |
* @internal | |
* @return boolean | |
*/ | |
public function canModifyToMatch() | |
{ | |
return ! empty($this->parent_object); | |
} | |
/** | |
* Internal: Modifies the supplied object so that it relates to the parent object. | |
* | |
* @internal | |
* @return void | |
*/ | |
public function modifyToMatch($object) | |
{ | |
if($this->reverse) | |
{ | |
$this->parent_object->tracks = $object; | |
$this->parent_object->artist_id = $object->id; | |
} | |
else | |
{ | |
$object->tracks = $this->parent_object; | |
$object->artist_id = $this->parent_object->id; | |
} | |
} | |
/** | |
* Internal: Modifies the supplied object so that it does not relate to the parent object. | |
* | |
* @internal | |
* @return void | |
*/ | |
public function modifyToNotMatch($object) | |
{ | |
if($this->reverse) | |
{ | |
$this->parent_object->artist_id = null; | |
} | |
else | |
{ | |
$object->artist_id = null; | |
} | |
} | |
/** | |
* Internal: Creates the relationship condition used in the ON part of JOIN if | |
* no $parent_object is present, otherwise it filters Artist by tracks. | |
* | |
* @return string | |
*/ | |
public function __toString() | |
{ | |
if($this->parent_object) | |
{ | |
return $this->alias.'.`id` = '.ArtistCollection::$db->escape($this->parent_object->artist_id); | |
} | |
else | |
{ | |
return $this->parent_alias.'.`id` = '.$this->alias.'.`artist_id`'; | |
} | |
} | |
} | |
/** | |
* A class which handles relations between Artist and albums: it can produce join conditions | |
* for Artist to albums, filter Artist by albums and finally establish a relation between Artist and albums. | |
*/ | |
class ArtistAlbumsRelation implements Rdm_Collection_FilterInterface | |
{ | |
public $id = ArtistCollection::Albums; | |
public $type = 'relation.HAS_MANY'; | |
public $reverse = false; | |
public $parent_object; | |
public $alias; | |
public $parent_alias; | |
/** | |
* Internal: Creates a new Relation filter which filters Artist by their | |
* related albums, this will put it in "reverse mode", if an $object is not present, "reverse mode" won't be triggered. | |
* | |
* So for relating from Artist to albums, ie. JOIN albums to | |
* the Artist query, use new ArtistAlbumsRelation; | |
* | |
* To filter Artist by an already fetched albums object (\Album), | |
* ie. WHERE Artist.key = $related_object->otherkey, | |
* use new ArtistAlbumsRelation($related_object, 'alias for Artist'); | |
* | |
* @internal | |
* @param \Album|\Artist The instance to filter by, if the entities to fetch | |
* should relate to this specific instance, optional. | |
* If it is a \Album entity, it will relate to \Artist, and reverse if it is a \Artist. | |
* @param string The alias of the table containing Artist which should be filtered by | |
* $object. | |
*/ | |
public function __construct($object = null, $alias = '') | |
{ | |
if( ! is_null($object)) | |
{ | |
$this->parent_object = $object; | |
if($object instanceof \Album) | |
{ | |
// Relating from Album to Artist | |
$this->reverse = true; | |
} | |
} | |
$this->alias = $alias; | |
} | |
/** | |
* Internal: Sets the aliases to use by this relation filter to create the ON filter | |
* between the two tables. | |
* | |
* @internal | |
* @param string | |
* @param string | |
*/ | |
public function setAliases($alias, $parent_alias) | |
{ | |
$this->alias = $alias; | |
$this->parent_alias = $parent_alias; | |
} | |
/** | |
* Internal: Returns true if this relation filter can modify a related object. | |
* | |
* @internal | |
* @return boolean | |
*/ | |
public function canModifyToMatch() | |
{ | |
return ! empty($this->parent_object); | |
} | |
/** | |
* Internal: Modifies the supplied object so that it relates to the parent object. | |
* | |
* @internal | |
* @return void | |
*/ | |
public function modifyToMatch($object) | |
{ | |
if($this->reverse) | |
{ | |
$this->parent_object->albums = $object; | |
$this->parent_object->artist_id = $object->id; | |
} | |
else | |
{ | |
$object->albums = $this->parent_object; | |
$object->artist_id = $this->parent_object->id; | |
} | |
} | |
/** | |
* Internal: Modifies the supplied object so that it does not relate to the parent object. | |
* | |
* @internal | |
* @return void | |
*/ | |
public function modifyToNotMatch($object) | |
{ | |
if($this->reverse) | |
{ | |
$this->parent_object->artist_id = null; | |
} | |
else | |
{ | |
$object->artist_id = null; | |
} | |
} | |
/** | |
* Internal: Creates the relationship condition used in the ON part of JOIN if | |
* no $parent_object is present, otherwise it filters Artist by albums. | |
* | |
* @return string | |
*/ | |
public function __toString() | |
{ | |
if($this->parent_object) | |
{ | |
return $this->alias.'.`id` = '.ArtistCollection::$db->escape($this->parent_object->artist_id); | |
} | |
else | |
{ | |
return $this->parent_alias.'.`id` = '.$this->alias.'.`artist_id`'; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment