├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bench.php ├── bin └── neogen ├── box.json ├── build.php ├── composer.json ├── composer.lock ├── neogen.cypher ├── neogen.yml ├── phpspec.yml ├── phpunit.xml ├── spec ├── Graph │ ├── NodeSpec.php │ └── RelationshipSpec.php ├── NeogenSpec.php ├── Parser │ ├── ParserManagerSpec.php │ ├── YamlFileParserSpec.php │ ├── _invalid-schema.yml │ └── _schema1.yml ├── Processor │ └── GraphProcessorSpec.php └── Schema │ ├── GraphSchemaBuilderSpec.php │ ├── GraphSchemaSpec.php │ ├── NodePropertySpec.php │ ├── NodeSpec.php │ ├── PropertySpec.php │ ├── RelationshipPropertySpec.php │ └── RelationshipSpec.php ├── src ├── Console │ ├── GenerateCommand.php │ ├── GenerateCypherCommand.php │ ├── InitCommand.php │ └── InitCypherCommand.php ├── Converter │ ├── ConverterInterface.php │ ├── CypherStatementsConverter.php │ ├── GraphJSONConverter.php │ └── StandardCypherConverter.php ├── DependencyInjection │ ├── Configuration.php │ └── NeogenExtension.php ├── Exception │ ├── CypherPatternException.php │ ├── GenerationExceptionInterface.php │ ├── NeogenException.php │ ├── ParseException.php │ ├── ParserNotFoundException.php │ └── SchemaDefinitionException.php ├── FakerProvider │ ├── Faker.php │ └── ProviderExtensionInterface.php ├── Graph │ ├── Graph.php │ ├── Node.php │ └── Relationship.php ├── GraphGenerator │ └── Generator.php ├── Helper │ ├── ArrayUtils.php │ └── CypherHelper.php ├── ModelLayer │ └── ModelLayerHandler.php ├── Neogen.php ├── Parser │ ├── CypherDefinition.php │ ├── CypherPattern.php │ ├── Definition │ │ ├── GraphDefinition.php │ │ ├── NodeDefinition.php │ │ └── PropertyDefinition.php │ ├── ParserInterface.php │ ├── ParserManager.php │ └── YamlFileParser.php ├── Processor │ └── GraphProcessor.php ├── Resources │ ├── config │ │ └── services.yml │ └── models │ │ ├── blog.yml │ │ ├── business.yml │ │ ├── people.yml │ │ ├── social.yml │ │ ├── system.yml │ │ └── user.yml ├── Schema │ ├── GraphSchema.php │ ├── GraphSchemaBuilder.php │ ├── Node.php │ ├── NodeProperty.php │ ├── Parser.php │ ├── PatternParser.php │ ├── Property.php │ ├── Relationship.php │ └── RelationshipProperty.php └── Util │ ├── GraphSerializer.php │ └── ObjectCollection.php ├── test.php ├── tests ├── Neoxygen │ └── Neogen │ │ └── Tests │ │ ├── BuilderTest.php │ │ └── Functional │ │ ├── SimpleYamlGraphTest.php │ │ └── simple_schema.yml ├── Unit │ └── Parser │ │ └── CypherPatternParserTest.php └── queries.txt └── tests_old └── Neoxygen └── Neogen └── Tests ├── Integration ├── CypherStatementsConverterTest.php ├── GraphJsonTest.php ├── IntegrationTest.php └── StandardCypherConverterTest.php ├── Parser └── NewCypherPatternTest.php ├── Processor └── VertEdgeProcessorTest.php └── ProcessorTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | neogen.cql 3 | tests/_reports 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.4 5 | - 5.5 6 | - 5.6 7 | 8 | env: 9 | - NEO_VERSION="2.1.5" 10 | - NEO_VERSION="2.1.6" 11 | - NEO_VERSION="2.2.0-M02" 12 | 13 | before_install: 14 | - sudo apt-get update > /dev/null 15 | - wget dist.neo4j.org/neo4j-community-$NEO_VERSION-unix.tar.gz > null 16 | - tar -xzf neo4j-community-$NEO_VERSION-unix.tar.gz > null 17 | - neo4j-community-$NEO_VERSION/bin/neo4j start > null 18 | 19 | before_script: 20 | - composer self-update 21 | - composer install --prefer-source --no-interaction 22 | 23 | script: 24 | - vendor/bin/phpunit --verbose 25 | - vendor/bin/phpspec run -fpretty 26 | 27 | notifications: 28 | email: "willemsen.christophe@gmail.com" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Christophe Willemsen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Neogen 2 | 3 | [![Build Status](https://travis-ci.org/neoxygen/neo4j-neogen.svg?branch=master)](https://travis-ci.org/neoxygen/neo4j-neogen) 4 | 5 | ## Graph Generator for Neo4j 6 | 7 | The library ease the generation of test graphs. The [faker](https://github.com/fzaninotto/faker) library is also used to generate random property values. 8 | 9 | You can define your graph model in YAML or in a slightly modified Cypher pattern. 10 | 11 | ## This is in development 12 | 13 | The library is in its early stage and is targetted to be used in developement environment. 14 | 15 | This library is the heart of the popular [Graphgen web application](http://graphgen.neoxygen.io) 16 | 17 | ## Usage 18 | 19 | ### Download the library : 20 | 21 | ```bash 22 | git clone https://github.com/graphaware/neo4j-neogen 23 | 24 | cd neogen 25 | ``` 26 | 27 | ### Define your graph model in Cypher, refer to Graphgen for documentation reference : 28 | 29 | ```cypher 30 | (p:Person:User {uuid: uuid, dob:{dateTimeBetween:["-65years","-18 years"]}} *10) 31 | (c:Company {name: company, slogan: catchPhrase} *5) 32 | (p)-[:WORKS_AT *n..1]->(c) 33 | ``` 34 | 35 | ### Define your graph model in YAML : 36 | 37 | ```yaml 38 | connection: 39 | scheme: http 40 | host: localhost 41 | port: 7474 42 | 43 | nodes: 44 | persons: 45 | label: Person 46 | count: 50 47 | properties: 48 | firstname: firstName 49 | lastname: lastName 50 | 51 | companies: 52 | label: Company 53 | count: 10 54 | properties: 55 | name: company 56 | description: catchPhrase 57 | 58 | relationships: 59 | person_works_for: 60 | start: persons 61 | end: companies 62 | type: WORKS_AT 63 | mode: n..1 64 | 65 | friendships: 66 | start: persons 67 | end: persons 68 | type: KNOWS 69 | mode: n..n 70 | ``` 71 | 72 | #### Run the generate command : 73 | 74 | ```bash 75 | vendor/bin/neogen generate 76 | ``` 77 | 78 | or you may want to export the generation queries to a file, handy for importing it in the Neo4j Console : 79 | 80 | ```bash 81 | ./vendor/bin/neogen generate --export="myfile.cql" 82 | ``` 83 | 84 | See the results in your graph browser ! 85 | 86 | #### Quick configuration precisions: 87 | 88 | * When defining properties types (like company, firstName, ...), these types refers to the standard [faker](https://github.com/fzaninotto/faker) types. 89 | * count define the number of nodes you want 90 | * relationship mode : 1 for only one existing relationship per node, random for randomized number of relationships 91 | 92 | #### Properties parameters 93 | 94 | Sometimes you'll maybe want to define some parameters for your properties, for e.g. to have a realistic date of birth for `Person` nodes, 95 | you may want to be sure that the date will be between 50 years ago and 18 years ago if you set dob for people working for a company. 96 | 97 | ```yaml 98 | nodes: 99 | persons: 100 | label: Person 101 | count: 10 102 | properties: 103 | firstname: firstName 104 | date_of_birth: { type: "dateTimeBetween", params: ["-50 years", "-18 years"]} 105 | 106 | relationships: 107 | person_works_for: 108 | start: persons 109 | end: companies 110 | type: WORKS_AT 111 | mode: random 112 | properties: 113 | since: { type: "dateTimeBetween", params: ["-10 years", "now"]} 114 | ``` 115 | 116 | ### Define your graph model in Cypher : 117 | 118 | ``` 119 | //eg: 120 | (person:Person {firstname:firstName, lastname:lastName} *30)-[:KNOWS *n..n]->(person) 121 | (person)-[:WORKS_AT *n..1]->(company:Company {name:company, slogan:catchPhrase} *5) 122 | ``` 123 | 124 | For a complete description, see the [Graphgen documentation](http://graphgen.neoxygen.io/documentation) 125 | 126 | Generating the graph from a cypher pattern : 127 | 128 | ```bash 129 | ./bin/neogen generate-cypher --source="pattern.cypher" --export="export.gen" 130 | ``` 131 | 132 | Or you may want to import the graph directly in an accessible neo4j database : 133 | 134 | ```bash 135 | ./bin/neogen generate-cypher --source="pattern.cypher" --export-db="localhost:7474" 136 | ``` 137 | 138 | 139 | --- 140 | 141 | ## Development 142 | 143 | Contributions, feedbacks, requirements welcome. Shoot me by opening issues or PR or on twitter : [@ikwattro](https://twitter.com/ikwattro) 144 | 145 | -------------------------------------------------------------------------------- /bench.php: -------------------------------------------------------------------------------- 1 | build(); 12 | 13 | $gsb = new GraphSchemaBuilder(); 14 | $file = __DIR__.'/neogen.yml'; 15 | $p = $neogen->getParserManager()->getParser('YamlParser'); 16 | $userSchema = $p->parse($file); 17 | 18 | $def = $gsb->buildGraph($userSchema); 19 | $gen = $neogen->getGraphGenerator(); 20 | $g = $gen->generateGraph($def); 21 | 22 | print_r($g); 23 | -------------------------------------------------------------------------------- /bin/neogen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | setName('Neogen Console'); 31 | $application->setVersion(Neogen::getVersion()); 32 | $generateCommand = new GenerateCommand(); 33 | $application->add($generateCommand); 34 | $application->add(new InitCommand()); 35 | $application->add(new GenerateCypherCommand()); 36 | $application->add(new InitCypherCommand()); 37 | $application->run(); -------------------------------------------------------------------------------- /box.json: -------------------------------------------------------------------------------- 1 | { 2 | "chmod": "0755", 3 | "directories": [ 4 | "src" 5 | ], 6 | "files": [ 7 | "LICENSE" 8 | ], 9 | "finder": [ 10 | { 11 | "name": ["*.php", "*.yml"], 12 | "exclude": ["Tests", "phpspec", "spec", "phpunit"], 13 | "in": "vendor" 14 | } 15 | ], 16 | "git-version": "package_version", 17 | "main": "bin/neogen", 18 | "output": "neogen.phar", 19 | "stub": true 20 | } -------------------------------------------------------------------------------- /build.php: -------------------------------------------------------------------------------- 1 | build(); 9 | 10 | print_r($neogen); -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphaware/neo4j-neogen", 3 | "type": "library", 4 | "description": "Neo4j Graph generation engine", 5 | "keywords": [ 6 | "graph", 7 | "neo4j", 8 | "generation", 9 | "faker" 10 | ], 11 | "homepage": "http://graphaware.com", 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "name": "Christophe Willemsen", 16 | "email": "christophe@graphaware.com" 17 | } 18 | ], 19 | "require": { 20 | "php": ">=5.4", 21 | "fzaninotto/faker": "~1.4", 22 | "symfony/console": "~2.7", 23 | "symfony/yaml": "~2.7", 24 | "symfony/finder": "~2.7", 25 | "symfony/dependency-injection": "~2.7", 26 | "doctrine/collections": "~1.2", 27 | "neoxygen/neoclient": "~3.0", 28 | "ikwattro/faker-extra": "~0.3", 29 | "jms/serializer": "~0.16" 30 | }, 31 | "require-dev": { 32 | "phpunit/phpunit": "4.*", 33 | "phpspec/phpspec": "~2.0" 34 | }, 35 | "autoload": { 36 | "psr-4": { 37 | "Neoxygen\\Neogen\\": "src/", 38 | "GraphAware\\Neogen\\": "src/" 39 | } 40 | }, 41 | "autoload-dev": { 42 | "psr-4": { 43 | "Neoxygen\\Neogen\\Tests\\": "tests/Neoxygen/Neogen/Tests", 44 | "GraphAware\\Neogen\\Tests\\": "tests/" 45 | } 46 | }, 47 | "bin": ["bin/neogen"] 48 | } 49 | -------------------------------------------------------------------------------- /neogen.cypher: -------------------------------------------------------------------------------- 1 | (shop:Shop {name: company} *5) 2 | (item:Item {name: word, desc: sentence} *20) 3 | (shop)-[:SELLS *1..n]->(item) 4 | (customer:Customer {name: lastname} *30) 5 | (customer)-[:PURCHASED *n..n]->(item) -------------------------------------------------------------------------------- /neogen.yml: -------------------------------------------------------------------------------- 1 | connection: 2 | scheme: http 3 | host: localhost 4 | port: 7474 5 | 6 | nodes: 7 | users: 8 | labels: User 9 | count: 5000 10 | models: ["User"] 11 | properties: 12 | first_name: firstName 13 | uuid: uuid 14 | 15 | 16 | relationships: 17 | fileSpaceRels: 18 | start: users 19 | end: users 20 | type: FOLLOWS 21 | mode: n..n 22 | properties: 23 | 24 | -------------------------------------------------------------------------------- /phpspec.yml: -------------------------------------------------------------------------------- 1 | suites: 2 | suite: 3 | namespace: Neoxygen\Neogen 4 | psr4_prefix: Neoxygen\Neogen -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ./tests 7 | 8 | 9 | 10 | 11 | tests 12 | vendor 13 | bin 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /spec/Graph/NodeSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith('1234-5678'); 13 | } 14 | 15 | function it_is_initializable() 16 | { 17 | $this->shouldHaveType('Neoxygen\Neogen\Graph\Node'); 18 | } 19 | 20 | function it_should_have_an_id_on_construct() 21 | { 22 | $this->getId()->shouldReturn('1234-5678'); 23 | } 24 | 25 | function it_should_have_an_empty_collection_of_properties() 26 | { 27 | $this->getProperties()->shouldHaveCount(0); 28 | } 29 | 30 | function it_should_add_property_to_collection() 31 | { 32 | $this->addProperty('name', 'Chris'); 33 | $this->getProperties()->shouldHaveCount(1); 34 | } 35 | 36 | function it_should_return_or_not_it_has_the_given_property_name() 37 | { 38 | $this->addProperty('name', 'Chris'); 39 | $this->hasProperty('name')->shouldReturn(true); 40 | } 41 | 42 | function it_should_return_if_the_node_has_properties() 43 | { 44 | $this->hasProperties()->shouldReturn(false); 45 | $this->addProperty('name', 'Chris'); 46 | $this->hasProperties()->shouldReturn(true); 47 | } 48 | 49 | function it_should_add_labels_to_node() 50 | { 51 | $this->addLabels(array('Person', 'Adult')); 52 | $this->getLabels()->shouldHaveCount(2); 53 | } 54 | 55 | function it_should_return_the_first_label_by_default() 56 | { 57 | $this->addLabels(array('Person', 'Speaker')); 58 | $this->getLabel()->shouldReturn('Person'); 59 | } 60 | 61 | function it_should_throw_exception_if_the_node_has_no_label() 62 | { 63 | $this->shouldThrow('RuntimeException')->duringGetLabel(); 64 | $this->shouldThrow('RuntimeException')->duringGetLabels(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /spec/Graph/RelationshipSpec.php: -------------------------------------------------------------------------------- 1 | shouldHaveType('Neoxygen\Neogen\Graph\Relationship'); 13 | } 14 | 15 | function it_should_have_a_type() 16 | { 17 | $this->getType()->shouldBeNull(); 18 | $this->setType('RELATES_TO'); 19 | $this->getType()->shouldReturn('RELATES_TO'); 20 | } 21 | 22 | function it_should_have_a_source_id() 23 | { 24 | $this->getSourceId()->shouldBeNull(); 25 | $this->setSourceId('1234'); 26 | $this->getSourceId()->shouldReturn('1234'); 27 | } 28 | 29 | function it_should_have_a_target_id() 30 | { 31 | $this->getTargetId()->shouldBeNull(); 32 | $this->setTargetId('1234'); 33 | $this->getTargetId()->shouldReturn('1234'); 34 | } 35 | 36 | function it_should_have_a_source_label() 37 | { 38 | $this->getSourceLabel()->shouldBeNull(); 39 | $this->setSourceLabel('User'); 40 | $this->getSourceLabel()->shouldReturn('User'); 41 | } 42 | 43 | function it_should_have_a_target_label() 44 | { 45 | $this->getTargetLabel()->shouldBeNull(); 46 | $this->setTargetLabel('User'); 47 | $this->getTargetLabel()->shouldReturn('User'); 48 | } 49 | 50 | function it_should_have_an_emtpy_collection_of_properties() 51 | { 52 | $this->getProperties()->shouldHaveCount(0); 53 | } 54 | 55 | function it_should_set_properties() 56 | { 57 | $this->addProperty('name', 'Chris'); 58 | $this->getProperties()->shouldHaveCount(1); 59 | } 60 | 61 | function it_should_return_specific_property() 62 | { 63 | $this->addProperty('name', 'Chris'); 64 | $this->getProperty('name')->shouldReturn('Chris'); 65 | } 66 | 67 | function it_should_return_bool_if_properties() 68 | { 69 | $this->hasProperties()->shouldReturn(false); 70 | $this->addProperty('name', 'Chris'); 71 | $this->hasProperties()->shouldReturn(true); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /spec/NeogenSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedThrough('create', array()); 13 | } 14 | 15 | function it_is_initializable() 16 | { 17 | $this->shouldHaveType('Neoxygen\Neogen\Neogen'); 18 | } 19 | 20 | function it_has_a_di_container_by_default() 21 | { 22 | $this->getServiceContainer()->shouldNotBeNull(); 23 | } 24 | 25 | function it_has_a_default_empty_config() 26 | { 27 | $this->getConfiguration()->shouldHaveCount(0); 28 | } 29 | 30 | function it_should_return_a_neogen_instance_on_build() 31 | { 32 | $this->build()->shouldHaveType('Neoxygen\Neogen\Neogen'); 33 | } 34 | 35 | function it_should_have_a_non_frozen_container_by_default() 36 | { 37 | $this->getServiceContainer()->isFrozen()->shouldReturn(false); 38 | } 39 | 40 | function it_should_freeze_the_container_after_build() 41 | { 42 | $this->build(); 43 | $this->getServiceContainer()->isFrozen()->shouldReturn(true); 44 | } 45 | 46 | function it_should_throw_exception_when_accessing_service_and_container_is_not_frozen() 47 | { 48 | $this->shouldThrow('RuntimeException')->duringGetParserManager(); 49 | } 50 | 51 | function it_should_return_the_parser_manager() 52 | { 53 | $this->build(); 54 | $this->getParserManager()->shouldHaveType('Neoxygen\Neogen\Parser\ParserManager'); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /spec/Parser/ParserManagerSpec.php: -------------------------------------------------------------------------------- 1 | beADoubleOf('Neoxygen\Neogen\Parser\YamlFileParser'); 15 | } 16 | 17 | function it_is_initializable() 18 | { 19 | $this->shouldHaveType('Neoxygen\Neogen\Parser\ParserManager'); 20 | } 21 | 22 | function it_should_not_have_parsers_register_on_init() 23 | { 24 | $this->getParsers()->shouldHaveCount(0); 25 | } 26 | 27 | function it_should_add_a_parser_to_the_stack($parser) 28 | { 29 | $parser->getName()->willReturn('YamlParser'); 30 | $this->registerParser($parser); 31 | $this->getParsers()->shouldHaveCount(1); 32 | $this->getParsers()->shouldHaveKey('YamlParser'); 33 | } 34 | 35 | function it_should_be_possible_to_know_if_at_least_one_parser_is_registered($parser) 36 | { 37 | $this->hasParsers()->shouldReturn(false); 38 | $this->registerParser($parser); 39 | $this->hasParsers()->shouldReturn(true); 40 | } 41 | 42 | function it_should_return_bool_when_asking_if_parser_is_registered($parser) 43 | { 44 | $parser->getName()->willReturn('YamlParser'); 45 | $this->hasParser('YamlParser')->shouldReturn(false); 46 | $this->registerParser($parser); 47 | $this->hasParser('YamlParser')->shouldReturn(true); 48 | } 49 | 50 | function it_should_return_the_desired_parser($parser) 51 | { 52 | $parser->getName()->willReturn('YamlParser'); 53 | $this->registerParser($parser); 54 | $this->getParser('YamlParser')->shouldReturn($parser); 55 | } 56 | 57 | function it_should_throw_exception_when_parser_does_not_exist() 58 | { 59 | $this->shouldThrow('Neoxygen\Neogen\Exception\ParserNotFoundException')->duringGetParser('YamlParser'); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /spec/Parser/YamlFileParserSpec.php: -------------------------------------------------------------------------------- 1 | shouldHaveType('Neoxygen\Neogen\Parser\YamlFileParser'); 13 | } 14 | 15 | function it_should_implement_Parser_Interface() 16 | { 17 | $this->shouldImplement('Neoxygen\Neogen\Parser\ParserInterface'); 18 | } 19 | 20 | function it_should_have_a_filesystem_instance_on_init() 21 | { 22 | $this->getFS()->shouldHaveType('Symfony\Component\Filesystem\Filesystem'); 23 | } 24 | 25 | function it_should_throw_error_if_schema_file_not_found() 26 | { 27 | $this->shouldThrow('Neoxygen\Neogen\Exception\SchemaDefinitionException')->duringGetSchemaFileContent('/tmp/file.yml'); 28 | } 29 | 30 | function it_should_load_yaml_schema_file() 31 | { 32 | $this->getSchemaFileContent(__DIR__.'/_schema1.yml')->shouldBeArray(); 33 | } 34 | 35 | function it_should_throw_exception_if_yaml_is_not_valid() 36 | { 37 | $this->shouldThrow('Neoxygen\Neogen\Exception\SchemaDefinitionException')->duringGetSchemaFileContent(__DIR__.'/invalid-schema.yml'); 38 | } 39 | 40 | function it_should_return_a_schema_array() 41 | { 42 | $this->parse(__DIR__.'/_schema1.yml')->shouldBeArray(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /spec/Parser/_invalid-schema.yml: -------------------------------------------------------------------------------- 1 | nodes: 2 | trest: err 3 | '4 -------------------------------------------------------------------------------- /spec/Parser/_schema1.yml: -------------------------------------------------------------------------------- 1 | nodes: 2 | persons: 3 | label: Person 4 | count: 1 -------------------------------------------------------------------------------- /spec/Processor/GraphProcessorSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($faker); 17 | } 18 | 19 | function it_is_initializable() 20 | { 21 | $this->shouldHaveType('Neoxygen\Neogen\Processor\GraphProcessor'); 22 | } 23 | 24 | function it_should_define_percentage_of_target_nodes(ObjectCollection $collection, Relationship $relationship) 25 | { 26 | $collection->count()->willReturn(100); 27 | $relationship->hasPercentage()->willReturn(false); 28 | $relationship->getEndNode()->willReturn('person'); 29 | $this->getTargetNodesCount($relationship, $collection)->shouldBe(60); 30 | 31 | $collection->count()->willReturn(1); 32 | $relationship->hasPercentage()->willReturn(false); 33 | $relationship->getEndNode()->willReturn('person'); 34 | $this->getTargetNodesCount($relationship, $collection)->shouldBe(1); 35 | 36 | $collection->count()->willReturn(1000); 37 | $relationship->hasPercentage()->willReturn(false); 38 | $this->getTargetNodesCount($relationship, $collection)->shouldBe(200); 39 | 40 | $collection->count()->willReturn(100); 41 | $relationship->hasPercentage()->willReturn(true); 42 | $relationship->getPercentage()->willReturn(65); 43 | $this->getTargetNodesCount($relationship, $collection)->shouldBe(65); 44 | 45 | $collection->count()->willReturn(1000); 46 | $relationship->hasPercentage()->willReturn(true); 47 | $relationship->getPercentage()->willReturn(45); 48 | $this->getTargetNodesCount($relationship, $collection)->shouldBe(450); 49 | } 50 | 51 | function it_should_calculate_the_approx_end_connected_nodes_depending_on_counts() 52 | { 53 | $this->calculateApproxTargetNodes(1,1)->shouldReturn(1); 54 | $this->calculateApproxTargetNodes(10,11)->shouldReturn(1); 55 | $this->calculateApproxTargetNodes(1,5)->shouldReturn(5); 56 | $this->calculateApproxTargetNodes(2,5)->shouldReturn(2); 57 | $this->calculateApproxTargetNodes(3,6)->shouldReturn(2); 58 | $this->calculateApproxTargetNodes(6,3)->shouldReturn(1); 59 | $this->calculateApproxTargetNodes(30,11)->shouldReturn(1); 60 | $this->calculateApproxTargetNodes(30,10)->shouldReturn(1); 61 | $this->calculateApproxTargetNodes(5, 20)->shouldReturn(4); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /spec/Schema/GraphSchemaBuilderSpec.php: -------------------------------------------------------------------------------- 1 | shouldHaveType('Neoxygen\Neogen\Schema\GraphSchemaBuilder'); 13 | } 14 | 15 | function it_should_create_node_elements() 16 | { 17 | $this->buildNode('person', $this->getNodeArray())->shouldHaveType('Neoxygen\Neogen\Schema\Node'); 18 | } 19 | 20 | function it_should_create_property_elements() 21 | { 22 | $this->buildNodeProperty('first_name', $this->getNodeArray()['properties']['first_name'])->shouldBeAnInstanceOf('Neoxygen\Neogen\Schema\Property'); 23 | } 24 | 25 | function it_should_create_relationships() 26 | { 27 | $this->buildRelationship($this->getRelArray())->shouldHaveType('Neoxygen\Neogen\Schema\Relationship'); 28 | } 29 | 30 | private function getNodeArray() 31 | { 32 | return array( 33 | 'labels' => 'Person', 34 | 'count' => 10, 35 | 'properties' => array( 36 | 'first_name' => 'firstName', 37 | 'last_name' => 'lastName', 38 | 'birth_date' => array( 39 | 'type' => 'dateTimeBetween', 40 | 'params' => array( 41 | '-65 years', 42 | '-18 years' 43 | ) 44 | ) 45 | ) 46 | ); 47 | } 48 | 49 | private function getRelArray() 50 | { 51 | return array( 52 | 'type' => 'RELATES_TO', 53 | 'start' => 'person', 54 | 'end' => 'company', 55 | 'mode' => 'n..1', 56 | 'properties' => array( 57 | 'since' => 'dateTime' 58 | ) 59 | ); 60 | } 61 | 62 | 63 | } 64 | -------------------------------------------------------------------------------- /spec/Schema/GraphSchemaSpec.php: -------------------------------------------------------------------------------- 1 | shouldHaveType('Neoxygen\Neogen\Schema\GraphSchema'); 15 | } 16 | 17 | function it_should_have_an_empty_collection_of_nodes_by_default() 18 | { 19 | $this->getNodes()->shouldHaveType('Neoxygen\Neogen\Util\ObjectCollection'); 20 | $this->getNodes()->shouldHaveCount(0); 21 | } 22 | 23 | function it_should_have_an_empty_collection_of_relationships_by_default() 24 | { 25 | $this->getRelationships()->shouldHaveType('Neoxygen\Neogen\Util\ObjectCollection'); 26 | $this->getRelationships()->shouldHaveCount(0); 27 | } 28 | 29 | function it_should_add_node_to_collection(Node $node) 30 | { 31 | $this->addNode($node); 32 | $this->getNodes()->shouldHaveCount(1); 33 | } 34 | 35 | function it_should_add_relationships_to_collection(Relationship $relationship) 36 | { 37 | $this->addRelationship($relationship); 38 | $this->getRelationships()->shouldHaveCount(1); 39 | } 40 | 41 | function it_should_throw_error_when_adding_nodes_with_same_identifier(Node $node) 42 | { 43 | $node->getIdentifier()->willReturn('person'); 44 | $this->addNode($node); 45 | $this->shouldThrow('Neoxygen\Neogen\Exception\SchemaDefinitionException')->duringAddNode($node); 46 | } 47 | 48 | function it_should_throw_error_when_creating_duplicated_relationships(Relationship $relationship) 49 | { 50 | $relationship->getType()->willReturn('RELATES'); 51 | $relationship->getStartNode()->willReturn('person'); 52 | $relationship->getEndNode()->willReturn('company'); 53 | $this->addRelationship($relationship); 54 | $this->shouldThrow('Neoxygen\Neogen\Exception\SchemaDefinitionException')->duringAddRelationship($relationship); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /spec/Schema/NodePropertySpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith('name'); 13 | } 14 | 15 | function it_is_initializable() 16 | { 17 | $this->shouldHaveType('Neoxygen\Neogen\Schema\NodeProperty'); 18 | } 19 | 20 | function it_should_extend_base_property() 21 | { 22 | $this->shouldBeAnInstanceOf('Neoxygen\Neogen\Schema\Property'); 23 | } 24 | 25 | function it_should_not_be_indexed_by_default() 26 | { 27 | $this->isIndexed()->shouldReturn(false); 28 | } 29 | 30 | function it_should_have_an_index_mutator() 31 | { 32 | $this->setIndexed(); 33 | $this->isIndexed()->shouldReturn(true); 34 | } 35 | 36 | function it_should_not_be_unique_by_default() 37 | { 38 | $this->isUnique()->shouldReturn(false); 39 | } 40 | 41 | function it_should_have_a_unique_mutator() 42 | { 43 | $this->setUnique(); 44 | $this->isUnique()->shouldReturn(true); 45 | } 46 | 47 | function it_can_not_be_unique_and_indexed() 48 | { 49 | $this->setIndexed(); 50 | $this->setUnique(); 51 | $this->isIndexed()->shouldReturn(false); 52 | 53 | $this->setIndexed(); 54 | $this->isUnique()->shouldReturn(false); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /spec/Schema/NodeSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith('person'); 14 | } 15 | 16 | function it_is_initializable() 17 | { 18 | $this->shouldHaveType('Neoxygen\Neogen\Schema\Node'); 19 | } 20 | 21 | function it_should_have_an_identifier_on_init() 22 | { 23 | $this->getIdentifier()->shouldReturn('person'); 24 | } 25 | 26 | function it_should_have_an_empty_collection_of_properties_on_init() 27 | { 28 | $this->getProperties()->shouldHaveType('Neoxygen\Neogen\Util\ObjectCollection'); 29 | } 30 | 31 | function it_should_add_a_property_to_collection(NodeProperty $property) 32 | { 33 | $this->addProperty($property); 34 | $this->getProperties()->count()->shouldReturn(1); 35 | } 36 | 37 | function it_should_count_the_properties_collection(NodeProperty $property) 38 | { 39 | $this->addProperty($property); 40 | $this->getPropertiesCount()->shouldReturn(1); 41 | } 42 | 43 | function it_should_tell_whether_or_not_there_are_properties(NodeProperty $property) 44 | { 45 | $this->hasProperties()->shouldReturn(false); 46 | $this->addProperty($property); 47 | $this->hasProperties()->shouldReturn(true); 48 | } 49 | 50 | function it_should_return_the_indexed_properties(NodeProperty $property) 51 | { 52 | $property->isIndexed()->willReturn(true); 53 | $this->addProperty($property); 54 | $this->getIndexedProperties()->shouldBeArray(); 55 | $this->getIndexedProperties()->shouldHaveCount(1); 56 | } 57 | 58 | function it_should_return_the_unique_properties(NodeProperty $property) 59 | { 60 | $this->getUniqueProperties()->shouldHaveCount(0); 61 | $property->isUnique()->willReturn(true); 62 | $this->addProperty($property); 63 | $this->getUniqueProperties()->shouldBeArray(); 64 | $this->getUniqueProperties()->shouldHaveCount(1); 65 | } 66 | 67 | function it_should_not_duplicate_properties(NodeProperty $property) 68 | { 69 | $property->getName()->willReturn('first_name'); 70 | $this->addProperty($property); 71 | $this->addProperty($property); 72 | $this->getProperties()->shouldHaveCount(1); 73 | } 74 | 75 | function it_should_not_have_labels_by_default() 76 | { 77 | $this->getLabels()->shouldHaveCount(0); 78 | } 79 | 80 | function it_should_be_possible_to_add_label() 81 | { 82 | $this->addLabel('Person'); 83 | $this->getLabels()->shouldHaveCount(1); 84 | } 85 | 86 | function it_should_be_possible_to_add_multiple_labels() 87 | { 88 | $this->addLabels(array('Person', 'Adult', 'Speaker')); 89 | $this->getLabels()->shouldHaveCount(3); 90 | $this->hasLabel('Adult')->shouldReturn(true); 91 | } 92 | 93 | function it_should_not_duplicate_labels() 94 | { 95 | $this->addLabel('Person'); 96 | $this->addLabels(array('Person', 'Adult')); 97 | $this->getLabels()->shouldHaveCount(2); 98 | } 99 | 100 | function it_should_tell_whether_or_not_the_node_has_a_specified_property(NodeProperty $property) 101 | { 102 | $property->getName()->willReturn('first_name'); 103 | $this->addProperty($property); 104 | $this->hasProperty('first_name')->shouldReturn(true); 105 | $this->hasProperty('last_name')->shouldReturn(false); 106 | } 107 | 108 | function it_should_have_a_default_amount_of_1() 109 | { 110 | $this->getAmount()->shouldReturn(1); 111 | $this->setAmount(25); 112 | $this->getAmount()->shouldReturn(25); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /spec/Schema/PropertySpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith('prop'); 13 | } 14 | 15 | function it_is_initializable() 16 | { 17 | $this->shouldHaveType('Neoxygen\Neogen\Schema\Property'); 18 | } 19 | 20 | function it_should_have_a_name_on_construct() 21 | { 22 | $this->getName()->shouldNotBeNull(); 23 | } 24 | 25 | function it_should_not_have_a_provider_by_default() 26 | { 27 | $this->getProvider()->shouldBeNull(); 28 | } 29 | 30 | function it_should_have_a_provider_mutator() 31 | { 32 | $this->setProvider('unixTime'); 33 | $this->getProvider()->shouldReturn('unixTime'); 34 | } 35 | 36 | function it_should_accept_provider_arguments() 37 | { 38 | $this->setProvider('numberBetween', array(1, 5)); 39 | $this->getArguments()->shouldBeArray(); 40 | } 41 | 42 | function it_should_return_bool_if_it_has_arguments_or_not() 43 | { 44 | $this->hasArguments()->shouldReturn(false); 45 | $this->setProvider('numberBetween', array(1,5)); 46 | $this->hasArguments()->shouldReturn(true); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /spec/Schema/RelationshipPropertySpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith('since'); 13 | } 14 | 15 | function it_is_initializable() 16 | { 17 | $this->shouldHaveType('Neoxygen\Neogen\Schema\RelationshipProperty'); 18 | } 19 | 20 | function it_should_extend_base_property() 21 | { 22 | $this->shouldBeAnInstanceOf('Neoxygen\Neogen\Schema\Property'); 23 | } 24 | 25 | function it_should_not_be_unique_by_default() 26 | { 27 | $this->isUnique()->shouldReturn(false); 28 | } 29 | 30 | function it_should_have_a_unique_mutator() 31 | { 32 | $this->setUnique(); 33 | $this->isUnique()->shouldReturn(true); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /spec/Schema/RelationshipSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith('person', 'company', 'RELATES'); 15 | } 16 | 17 | function it_is_initializable() 18 | { 19 | $this->shouldHaveType('Neoxygen\Neogen\Schema\Relationship'); 20 | } 21 | 22 | function it_should_have_a_type_by_default() 23 | { 24 | $this->getType()->shouldReturn('RELATES'); 25 | } 26 | 27 | function it_should_have_an_empty_collection_of_properties() 28 | { 29 | $this->getProperties()->shouldHaveType('Neoxygen\Neogen\Util\ObjectCollection'); 30 | $this->getProperties()->shouldHaveCount(0); 31 | } 32 | 33 | function it_should_add_a_property_to_the_collection(RelationshipProperty $property) 34 | { 35 | $this->addProperty($property); 36 | $this->getProperties()->shouldHaveCount(1); 37 | } 38 | 39 | function it_should_return_whether_or_not_there_are_properties(RelationshipProperty $property) 40 | { 41 | $this->hasProperties()->shouldReturn(false); 42 | $this->addProperty($property); 43 | $this->hasProperties()->shouldReturn(true); 44 | } 45 | 46 | function it_should_not_duplicate_properties(RelationshipProperty $property) 47 | { 48 | $property->getName()->willReturn('since'); 49 | $this->addProperty($property); 50 | $property->getName()->willReturn('since'); 51 | $this->addProperty($property); 52 | $this->getProperties()->shouldHaveCount(1); 53 | } 54 | 55 | function it_should_tell_if_the_relationship_has_the_specified_property_name(RelationshipProperty $property) 56 | { 57 | $property->getName()->willReturn('since'); 58 | $this->addProperty($property); 59 | $this->hasProperty('since')->shouldReturn(true); 60 | $this->hasProperty('weight')->shouldReturn(false); 61 | } 62 | 63 | function it_should_have_a_start_and_end_node_by_default() 64 | { 65 | $this->getStartNode()->shouldReturn('person'); 66 | $this->getEndNode()->shouldReturn('company'); 67 | } 68 | 69 | function it_should_not_have_a_default_cardinality() 70 | { 71 | $this->getCardinality()->shouldBeNull(); 72 | $this->setCardinality('1..1'); 73 | $this->getCardinality()->shouldReturn('1..1'); 74 | } 75 | 76 | function it_should_not_have_a_percentage_by_default() 77 | { 78 | $this->getPercentage()->shouldReturn(null); 79 | $this->hasPercentage()->shouldReturn(false); 80 | } 81 | 82 | function it_should_set_percentage() 83 | { 84 | $this->setPercentage(10); 85 | $this->getPercentage()->shouldReturn(10); 86 | $this->hasPercentage()->shouldReturn(true); 87 | } 88 | 89 | function it_should_not_accept_null_percentage() 90 | { 91 | $this->shouldThrow('Neoxygen\Neogen\Exception\SchemaDefinitionException')->duringSetPercentage(0); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Console/GenerateCommand.php: -------------------------------------------------------------------------------- 1 | setName('generate') 21 | ->setDescription('Generate fixtures based on "neogen.yml" file') 22 | ->addOption( 23 | 'export', 24 | null, 25 | InputOption::VALUE_OPTIONAL, 26 | 'If the generation queries should be exported to a file rather than loaded in the database?', 27 | null 28 | ) 29 | ; 30 | } 31 | 32 | protected function execute(InputInterface $input, OutputInterface $output) 33 | { 34 | $neogen = Neogen::create() 35 | ->build(); 36 | 37 | $yamlParser = $neogen->getParserManager()->getParser('yaml'); 38 | $filePath = getcwd() . '/neogen.yml'; 39 | $schema = $yamlParser->parse($filePath); 40 | $start = microtime(true); 41 | $g = $neogen->generateGraph($schema); 42 | $end = microtime(true); 43 | print($end - $start); 44 | echo PHP_EOL; 45 | 46 | $serializer = $neogen->getServiceContainer()->get('neogen.graph_serializer'); 47 | //$json = $serializer->serializeGraphToJson($g); 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Console/GenerateCypherCommand.php: -------------------------------------------------------------------------------- 1 | fs = new Filesystem(); 33 | } 34 | protected function configure() 35 | { 36 | $this 37 | ->setName('generate-cypher') 38 | ->setDescription('Generate fixtures based on "neogen.cql" file') 39 | ->addOption( 40 | 'source', 41 | null, 42 | InputOption::VALUE_REQUIRED, 43 | '/neogen.cql') 44 | ->addOption( 45 | 'export', 46 | null, 47 | InputOption::VALUE_OPTIONAL, 48 | 'Export generated cypher statements into file', 49 | null 50 | ) 51 | ->addOption( 52 | 'export-db', 53 | null, 54 | InputOption::VALUE_OPTIONAL, 55 | 'Database connection settings', 56 | null) 57 | ; 58 | } 59 | 60 | protected function execute(InputInterface $input, OutputInterface $output) 61 | { 62 | $start = microtime(true); 63 | 64 | $output->writeln('Locating fixtures file'); 65 | $fixtures_file = getcwd().'/'.$input->getOption('source'); 66 | 67 | if (!$this->fs->exists($fixtures_file)) { 68 | throw new \InvalidArgumentException(sprintf('The source file %s does not exist', $fixtures_file)); 69 | } 70 | 71 | $this->source = file_get_contents($fixtures_file); 72 | 73 | if ($input->getOption('export') == null && $input->getOption('export-db') == null) { 74 | $output->writeln('The --export or --export-db option is mandatory'); 75 | throw new \InvalidArgumentException('The --export or --export-db option is mandatory'); 76 | } 77 | 78 | if ($input->getOption('export') != null && $input->getOption('export-db') != null) { 79 | $output->writeln('You can only use one of "export" OR "export-db"'); 80 | throw new \InvalidArgumentException('There can be only one of export or export-db'); 81 | } 82 | 83 | if ($input->getOption('export') != null) { 84 | $exportFile = $input->getOption('export'); 85 | $this->exportToFile($exportFile, $this->generateGraph(), $output); 86 | } elseif ($input->getOption('export-db') != null) { 87 | $client = $this->getDBConnection($input->getOption('export-db')); 88 | $this->exportToDB($this->generateGraph(), $output, $client); 89 | } 90 | 91 | $end = microtime(true); 92 | $diff = $end - $start; 93 | $output->writeln('Graph generation done in '.$diff.' seconds'); 94 | } 95 | 96 | private function generateGraph() 97 | { 98 | $gen = new Neogen(); 99 | $graph = $gen->generateGraphFromCypher($this->source); 100 | 101 | return $graph; 102 | } 103 | 104 | private function exportToFile($file, $graph, OutputInterface $output) 105 | { 106 | $converter = new StandardCypherConverter(); 107 | $converter->convert($graph); 108 | $statements = $converter->getStatements(); 109 | $txt = ''; 110 | foreach ($statements as $statement) { 111 | $txt .= $statement."\n"; 112 | } 113 | $exportFilePath = getcwd().'/'.$file; 114 | if ($this->fs->exists($exportFilePath)) { 115 | $this->fs->copy($exportFilePath, $exportFilePath.'.backup'); 116 | } 117 | $this->fs->dumpFile($exportFilePath, $txt); 118 | $output->writeln('Exporting the queries to '.$exportFilePath.''); 119 | } 120 | 121 | private function exportToDB($graph, OutputInterface $output, Client $client) 122 | { 123 | $converter = new CypherStatementsConverter(); 124 | $converter->convert($graph); 125 | 126 | try { 127 | $constraints = 0; 128 | foreach ($converter->getConstraintStatements() as $statement) { 129 | $client->sendCypherQuery($statement['statement']); 130 | $constraints++; 131 | } 132 | $output->writeln('Created '.$constraints.' constraint(s)'); 133 | $nodes = 0; 134 | foreach ($converter->getNodeStatements() as $ns) { 135 | $client->sendCypherQuery($ns['statement'], $ns['parameters']); 136 | $nodes += count($ns['parameters']['props']); 137 | } 138 | $output->writeln('Created '.$nodes.' node(s)'); 139 | $edges = 0; 140 | foreach ($converter->getEdgeStatements() as $es) { 141 | $params = isset($es['parameters']) ? $es['parameters'] : ['pairs' => []]; 142 | $client->sendCypherQuery($es['statement'], $params); 143 | $edges += count($params['pairs']); 144 | } 145 | $output->writeln('Created '.$edges.' relationship(s)'); 146 | } catch (HttpException $e) { 147 | $output->writeln('Unable to connect to the database'.PHP_EOL.$e->getMessage().PHP_EOL.''); 148 | } 149 | } 150 | 151 | private function getDBConnection($dbConnection) 152 | { 153 | $defaults = [ 154 | 'scheme' => 'http', 155 | 'host' => 'localhost', 156 | 'port' => 7474 157 | ]; 158 | $conn = array_merge($defaults, parse_url($dbConnection)); 159 | if (!array_key_exists('scheme', $conn) || !array_key_exists('host', $conn) || !array_key_exists('port', $conn)) { 160 | throw new InvalidArgumentException('The connection settings does not seem to be correct'); 161 | } 162 | 163 | $client = ClientBuilder::create() 164 | ->addConnection('default', $conn['scheme'], $conn['host'], $conn['port']) 165 | ->build(); 166 | 167 | return $client; 168 | } 169 | 170 | } 171 | -------------------------------------------------------------------------------- /src/Console/InitCommand.php: -------------------------------------------------------------------------------- 1 | setName('init') 18 | ->setDescription('Generate a sample "neogen.yml" file') 19 | ; 20 | } 21 | 22 | protected function execute(InputInterface $input, OutputInterface $output) 23 | { 24 | 25 | $defaultSchema = [ 26 | 'connection' => [ 27 | 'scheme' => 'http', 28 | 'host' => 'localhost', 29 | 'port' => 7474 30 | ], 31 | 'nodes' => null, 32 | 'relationships' => null 33 | ]; 34 | 35 | $initFile = getcwd().'/neogen.yml'; 36 | if (!file_exists($initFile)) { 37 | $dumper = new Dumper(); 38 | $yaml = $dumper->dump($defaultSchema, 2); 39 | file_put_contents($initFile, $yaml); 40 | //Yaml::dump($defaultSchema, $initFile); 41 | $output->writeln('Graph Schema file created in "'.$initFile.'" modify it for your needs.'); 42 | } else { 43 | $output->writeln('The file "neoxygen.yml" already exist, delete it or modify it'); 44 | } 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Console/InitCypherCommand.php: -------------------------------------------------------------------------------- 1 | setName('init-cypher') 18 | ->setDescription('Generate a sample "neogen.cql" file') 19 | ; 20 | } 21 | 22 | protected function execute(InputInterface $input, OutputInterface $output) 23 | { 24 | $defaultQuery = "// Example : \n" . 25 | "(person:Person {firstname: firstName, lastname: lastName } *20)-[:KNOWS *n..n]->(person) \n" . 26 | "(person)-[:HAS *n..n]->(skill:Skill {name: progLanguage} *15) \n" . 27 | "(company:Company {name: company, desc: catchPhrase} *10)-[:LOOKS_FOR_COMPETENCE *n..n]->(skill) \n" . 28 | "(company)-[:LOCATED_IN *n..1]->(country:Country {name: country} *25) \n" . 29 | "(person)-[:LIVES_IN *n..1]->(country) \n"; 30 | 31 | $initFile = getcwd().'/neogen.cql'; 32 | if (!file_exists($initFile)) { 33 | file_put_contents($initFile, $defaultQuery); 34 | $output->writeln('Graph Schema file created in "'.$initFile.'" modify it for your needs.'); 35 | } else { 36 | $output->writeln('The file "neogen.cql" already exist, delete it or modify it'); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Converter/ConverterInterface.php: -------------------------------------------------------------------------------- 1 | getNodes() as $node) { 24 | $nodesByIdentifier[$node['identifier']][] = $node; 25 | if (!array_key_exists($node['identifier'], $identifierToLabelMap)) { 26 | $identifierToLabelMap[$node['identifier']] = $node['labels'][0]; 27 | $labels[] = $node['labels'][0]; 28 | } 29 | } 30 | 31 | // Creating constraints statements 32 | foreach ($nodesByIdentifier as $nodeIdentifier => $node) { 33 | $identifier = strtolower($nodeIdentifier); 34 | $label = $identifierToLabelMap[$nodeIdentifier]; 35 | 36 | $ccs = 'CREATE CONSTRAINT ON (' . $identifier . ':' . $label . ') ASSERT ' . $identifier . '.neogen_id IS UNIQUE'; 37 | $cst = ['statement' => $ccs]; 38 | $this->constraintsStatements[] = $cst; 39 | } 40 | 41 | // Creating node creation statements 42 | foreach ($nodesByIdentifier as $key => $type) { 43 | if (!isset($type[0])) { 44 | continue; 45 | } 46 | $node = $type[0]; 47 | $label = $node['labels'][0]; 48 | $labelsCount = count($node['labels']); 49 | $idx = strtolower($key); 50 | $q = 'UNWIND {props} as prop'.PHP_EOL; 51 | $q .= 'MERGE ('.$idx.':'.$label.' {neogen_id: prop.neogen_id})'.PHP_EOL; 52 | if ($labelsCount > 1) { 53 | $q .= 'SET '; 54 | $li = 1; 55 | foreach ($node['labels'] as $lbl) { 56 | if ($lbl !== $label) { 57 | $q .= $idx.' :'.$lbl; 58 | if ($li < $labelsCount) { 59 | $q .= ', '; 60 | } 61 | } 62 | $li++; 63 | } 64 | } 65 | $propsCount = count($node['properties']); 66 | if ($propsCount > 0) { 67 | if ($labelsCount > 1) { 68 | $q .= ', '; 69 | } else { 70 | $q .= 'SET '; 71 | } 72 | $i = 1; 73 | foreach ($node['properties'] as $property => $value) { 74 | $q .= $idx.'.'.$property.' = prop.properties.'.$property; 75 | if ($i < $propsCount) { 76 | $q .= ', '; 77 | } 78 | $i++; 79 | } 80 | } 81 | 82 | $nts = [ 83 | 'statement' => $q, 84 | 'parameters' => [ 85 | 'props' => $nodesByIdentifier[$key] 86 | ] 87 | ]; 88 | $this->nodeStatements[] = $nts; 89 | } 90 | 91 | foreach ($graph->getEdges() as $edge) { 92 | $edgeType = $edge['source_label'] . $edge['type'] . $edge['target_label']; 93 | if (!in_array($edgeType, $edgeTypes)) { 94 | $edgeTypes[] = $edgeType; 95 | } 96 | $edgesByType[$edgeType][] = $edge; 97 | } 98 | 99 | // Creating edge statements 100 | $i = 1; 101 | foreach ($edgesByType as $type => $rels) { 102 | if (!isset($type[0])) { 103 | continue; 104 | } 105 | $rel = $rels[0]; 106 | $q = 'UNWIND {pairs} as pair'.PHP_EOL; 107 | $q .= 'MATCH (start:'.$rel['source_label'].' {neogen_id: pair.source}), (end:'.$rel['target_label'].' {neogen_id: pair.target})'.PHP_EOL; 108 | $q .= 'MERGE (start)-[edge:'.$rel['type'].']->(end)'.PHP_EOL; 109 | $propsCount = count($rel['properties']); 110 | if ($propsCount > 0) { 111 | $q .= 'SET '; 112 | $i = 1; 113 | foreach ($rel['properties'] as $property => $value) { 114 | $q .= 'edge.'.$property.' = pair.properties.'.$property; 115 | if ($i < $propsCount) { 116 | $q .= ', '.PHP_EOL; 117 | } 118 | $i++; 119 | } 120 | } 121 | $ets = [ 122 | 'statement' => $q, 123 | 'parameters' => [ 124 | 'pairs' => $edgesByType[$type] 125 | ] 126 | ]; 127 | $this->edgeStatements[] = $ets; 128 | $i++; 129 | } 130 | 131 | $this->addRemoveIdsStatements($labels); 132 | 133 | return $this->getStatements(); 134 | 135 | } 136 | 137 | public function getStatements() 138 | { 139 | $statements = array( 140 | 'constraints' => $this->constraintsStatements, 141 | 'nodes' => $this->nodeStatements, 142 | 'edges' => $this->edgeStatements 143 | ); 144 | 145 | return $statements; 146 | } 147 | 148 | public function getEdgeStatements() 149 | { 150 | return $this->edgeStatements; 151 | } 152 | 153 | public function getConstraintStatements() 154 | { 155 | return $this->constraintsStatements; 156 | } 157 | 158 | public function getNodeStatements() 159 | { 160 | return $this->nodeStatements; 161 | } 162 | 163 | private function addRemoveIdsStatements(array $labels) 164 | { 165 | $i = 1; 166 | foreach ($labels as $label) { 167 | $q = 'MATCH (n'.$i.':'.$label.') REMOVE n'.$i.'.neogen_id;'; 168 | $statement = [ 169 | 'statement' => $q 170 | ]; 171 | 172 | $this->edgeStatements[] = $statement; 173 | $i++; 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/Converter/GraphJSONConverter.php: -------------------------------------------------------------------------------- 1 | nodes = []; 23 | $this->edges = []; 24 | $this->identifiers = []; 25 | $this->style = []; 26 | $this->clusterColors = []; 27 | $this->faker = Factory::create(); 28 | } 29 | 30 | public function convert(Graph $graph) 31 | { 32 | foreach ($graph->getNodes() as $node) { 33 | if (!in_array($node['identifier'], $this->identifiers)) { 34 | $this->identifiers[] = $node['identifier']; 35 | $this->setClusterForLabel($node['identifier']); 36 | } 37 | $n = []; 38 | $n['_id'] = $node['neogen_id']; 39 | $n['identifier'] = $node['identifier']; 40 | $n['properties'] = $node['properties']; 41 | $n['labels'] = $node['labels']; 42 | $n['cluster'] = $this->clusterColors[$node['identifier']]; 43 | $this->nodes[] = $n; 44 | } 45 | 46 | foreach ($graph->getEdges() as $edge) { 47 | $e = []; 48 | $e['_source'] = $edge['source']; 49 | $e['_target'] = $edge['target']; 50 | $e['type'] = $edge['type']; 51 | $e['properties'] = $edge['properties']; 52 | $e['source_label'] = $edge['source_label']; 53 | $e['target_label'] = $edge['target_label']; 54 | $this->edges[] = $e; 55 | } 56 | 57 | $this->buildStyle(); 58 | 59 | return $this->toJSON(); 60 | } 61 | 62 | public function buildStyle() 63 | { 64 | foreach ($this->identifiers as $path) { 65 | $style = []; 66 | $k = 'nodeStyle.identifier.'.$path; 67 | $color = $this->faker->hexcolor; 68 | $style[] = ['fill' => $color]; 69 | $this->style[$k] = $style; 70 | } 71 | } 72 | 73 | private function toJSON() 74 | { 75 | $g = [ 76 | 'style' => $this->style, 77 | 'nodes' => $this->nodes, 78 | 'edges' => $this->edges 79 | ]; 80 | 81 | $json = json_encode($g); 82 | 83 | return $json; 84 | } 85 | 86 | private function setClusterForLabel($label) 87 | { 88 | $cluster = $this->faker->numberBetween(0, 12); 89 | if (in_array($cluster, $this->clusterColors) && count($this->clusterColors) < 12) { 90 | $this->setClusterForLabel($label); 91 | } 92 | $this->clusterColors[$label] = $cluster; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Converter/StandardCypherConverter.php: -------------------------------------------------------------------------------- 1 | getNodes() as $node) { 20 | $nodesByIdentifier[$node['identifier']][] = $node; 21 | if (!array_key_exists($node['identifier'], $identifierToLabelMap)) { 22 | $identifierToLabelMap[$node['identifier']] = $node['labels'][0]; 23 | $labels[] = $node['labels'][0]; 24 | } 25 | } 26 | 27 | foreach ($nodesByIdentifier as $nodeIdentifier => $node) { 28 | $identifier = strtolower($nodeIdentifier); 29 | $label = $identifierToLabelMap[$nodeIdentifier]; 30 | 31 | $ccs = 'CREATE CONSTRAINT ON (' . $identifier . ':' . $label . ') ASSERT ' . $identifier . '.neogen_id IS UNIQUE;'; 32 | $this->statements[] = $ccs; 33 | } 34 | 35 | $i = 1; 36 | foreach ($graph->getNodes() as $node) { 37 | $label = $identifierToLabelMap[$node['identifier']]; 38 | $labelsCount = count($node['labels']); 39 | if (!isset($node['neogen_id']) && isset($node['_id'])) { 40 | $node['neogen_id'] = $node['_id']; 41 | } 42 | $identifier = 'n'.$i; 43 | $statement = 'MERGE ('.$identifier.':'.$label.' {neogen_id: \''.$node['neogen_id'].'\' })'; 44 | if ($labelsCount > 1) { 45 | $statement .= 'SET '; 46 | $li = 1; 47 | foreach ($node['labels'] as $lbl) { 48 | if ($lbl !== $label) { 49 | $statement .= $identifier.' :'.$lbl; 50 | if ($li < $labelsCount) { 51 | $statement .= ', '; 52 | } 53 | } 54 | $li++; 55 | } 56 | } 57 | $statement .= PHP_EOL; 58 | if (!empty($node['properties'])) { 59 | if ($labelsCount > 1) { 60 | $statement .= ', '; 61 | } else { 62 | $statement .= 'SET '; 63 | } 64 | $xi = 1; 65 | $propsCount = count($node['properties']); 66 | foreach ($node['properties'] as $prop => $value) { 67 | if (is_string($value)) { 68 | $val = '\''.addslashes($value).'\''; 69 | } elseif (is_int($value)) { 70 | $val = $value; 71 | } elseif (is_float($value)) { 72 | $val = $value; 73 | } else { 74 | $val = addslashes($value); 75 | } 76 | $statement .= $identifier.'.'.$prop.' = '.$val; 77 | if ($xi < $propsCount) { 78 | $statement .= ', '; 79 | } 80 | $xi++; 81 | } 82 | } 83 | $statement .= ';'; 84 | $this->statements[] = $statement; 85 | $i++; 86 | } 87 | 88 | $e = 1; 89 | foreach ($graph->getEdges() as $rel) { 90 | $starti = 's'.$e; 91 | $endi = 'e'.$e; 92 | $eid = 'edge'.$e; 93 | $q = 'MATCH ('.$starti.':'.$rel['source_label'].' {neogen_id: \''.$rel['source'].'\'}), ('.$endi.':'.$rel['target_label'].' { neogen_id: \''.$rel['target'].'\'})'.PHP_EOL; 94 | $q .= 'MERGE ('.$starti.')-['.$eid.':'.$rel['type'].']->('.$endi.')'.PHP_EOL; 95 | if (empty($rel['properties'])) { 96 | $q .= ';'; 97 | } 98 | if (!empty($rel['properties'])) { 99 | $q .= 'SET '; 100 | $xi = 1; 101 | $propsCount = count($rel['properties']); 102 | foreach ($rel['properties'] as $prop => $value) { 103 | if (is_string($value)) { 104 | $val = '\''.addslashes($value).'\''; 105 | } elseif (is_int($value)) { 106 | $val = $value; 107 | } elseif (is_float($value)) { 108 | $val = $value; 109 | } else { 110 | $val = addslashes($value); 111 | } 112 | $q .= $eid.'.'.$prop.' = '.$val; 113 | if ($xi < $propsCount) { 114 | $q .= ', '; 115 | } 116 | $xi++; 117 | } 118 | $q .= ';'; 119 | } 120 | $this->statements[] = $q; 121 | $e++; 122 | } 123 | 124 | $ssi = 1; 125 | foreach ($labels as $label) { 126 | $this->statements[] = 'MATCH (n'.$ssi.':'.$label.') REMOVE n'.$ssi.'.neogen_id;'; 127 | $ssi++; 128 | } 129 | 130 | return $this->statements; 131 | 132 | } 133 | 134 | public function getStatements() 135 | { 136 | return $this->statements; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | " NeoClient package 5 | * 6 | * (c) Neoxygen.io 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | * 11 | */ 12 | 13 | namespace Neoxygen\Neogen\DependencyInjection; 14 | 15 | use Symfony\Component\Config\Definition\ConfigurationInterface; 16 | use Symfony\Component\Config\Definition\Builder\TreeBuilder; 17 | 18 | class Configuration implements ConfigurationInterface 19 | { 20 | 21 | public function getConfigTreeBuilder() 22 | { 23 | $treeBuilder = new TreeBuilder(); 24 | $rootNode = $treeBuilder->root('neogen'); 25 | 26 | $rootNode->children() 27 | ->scalarNode('test')->defaultValue('cool')->end() 28 | ->arrayNode('faker') 29 | ->addDefaultsIfNotSet() 30 | ->children() 31 | ->scalarNode('class')->defaultValue('Neoxygen\Neogen\FakerProvider\Faker')->end() 32 | ->arrayNode('providers') 33 | ->prototype('array') 34 | ->children()->end() 35 | ->end() 36 | ->end() // end providers 37 | ->arrayNode('extensions') 38 | ->prototype('array')->end() 39 | ->end() 40 | ->end() // end faker 41 | ->end(); 42 | 43 | return $treeBuilder; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/DependencyInjection/NeogenExtension.php: -------------------------------------------------------------------------------- 1 | container = $container; 19 | $processor = new Processor(); 20 | $configuration = new Configuration(); 21 | 22 | $config = $processor->processConfiguration($configuration, $configs); 23 | $container->setParameter('faker.class', $config['faker']['class']); 24 | 25 | $loader = new YamlFileLoader( 26 | $container, 27 | new FileLocator(__DIR__.'/../Resources/config') 28 | ); 29 | 30 | $loader->load('services.yml'); 31 | 32 | } 33 | 34 | public function getAlias() 35 | { 36 | return 'neogen'; 37 | } 38 | 39 | public function getXsdValidationBasePath() 40 | { 41 | return false; 42 | } 43 | 44 | public function getNamespace() 45 | { 46 | return false; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Exception/CypherPatternException.php: -------------------------------------------------------------------------------- 1 | faker = Factory::create(); 17 | $this->arrayzedProviders[] = 'randomElement'; 18 | $this->arrayzedProviders[] = 'randomElements'; 19 | } 20 | 21 | public function generate($provider, $args = [], $seed = null, $unique = false, $arrayze = false) 22 | { 23 | if (in_array($provider, $this->arrayzedProviders)) { 24 | $arrayze = true; 25 | } 26 | 27 | if (null !== $seed) { 28 | $this->faker->seed((int) $seed); 29 | } 30 | 31 | if ($unique) { 32 | return $this->getUniqueValue($provider, $args, $arrayze); 33 | } 34 | 35 | return $this->getValue($provider, $args, $arrayze); 36 | } 37 | 38 | public function registerProvider(BaseProvider $provider) 39 | { 40 | return $this->faker->addProvider($provider); 41 | } 42 | 43 | public function registerProviderExtension(ProviderExtensionInterface $extension) 44 | { 45 | $classes = $extension->getProviders(); 46 | $arrP = $extension->getArrayzedProviders(); 47 | foreach ($classes as $class) { 48 | $this->registerProvider(new $class()); 49 | } 50 | foreach ($arrP as $arr) { 51 | $this->arrayzedProviders[] = $arr; 52 | } 53 | } 54 | 55 | private function getUniqueValue($provider, array $args, $arrayzeArgs = false) 56 | { 57 | if ($arrayzeArgs) { 58 | $value = call_user_func_array(array($this->faker->unique, $provider), array($args)); 59 | } else { 60 | $value = call_user_func_array(array($this->faker->unique, $provider), $args); 61 | } 62 | 63 | return $value; 64 | } 65 | 66 | private function getValue($provider, array $args, $arrayzeArgs = false) 67 | { 68 | if ($arrayzeArgs) { 69 | $value = call_user_func_array(array($this->faker, $provider), array($args)); 70 | } else { 71 | $value = call_user_func_array(array($this->faker, $provider), $args); 72 | } 73 | 74 | return $value; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/FakerProvider/ProviderExtensionInterface.php: -------------------------------------------------------------------------------- 1 | edges; 19 | } 20 | 21 | public function getNodes() 22 | { 23 | return $this->nodes; 24 | } 25 | 26 | public function setNodes(ObjectCollection $nodes) 27 | { 28 | $this->nodes = $nodes; 29 | } 30 | 31 | public function setEdges(ObjectCollection $edges) 32 | { 33 | $this->edges = $edges; 34 | } 35 | 36 | public function setSchema(GraphSchema $schema) 37 | { 38 | $this->schema = $schema; 39 | } 40 | 41 | public function getSchema() 42 | { 43 | return $this->schema; 44 | } 45 | 46 | public function toArray() 47 | { 48 | return array( 49 | 'nodes' => $this->nodes->toArray(), 50 | 'edges' => $this->edges->toArray(), 51 | 'schema' => $this->getSchema()->toArray() 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Graph/Node.php: -------------------------------------------------------------------------------- 1 | id = $id; 20 | } 21 | 22 | public function getId() 23 | { 24 | return $this->id; 25 | } 26 | 27 | public function getProperties() 28 | { 29 | return $this->properties; 30 | } 31 | 32 | public function addProperty($name, $value) 33 | { 34 | $this->properties[$name] = $value; 35 | } 36 | 37 | public function hasProperty($name) 38 | { 39 | if (array_key_exists($name, $this->properties)) { 40 | return true; 41 | } 42 | 43 | return false; 44 | } 45 | 46 | public function hasProperties() 47 | { 48 | if (!empty($this->properties)) { 49 | return true; 50 | } 51 | 52 | return false; 53 | } 54 | 55 | public function getLabels() 56 | { 57 | if (empty($this->labels)) { 58 | throw new \RuntimeException('The node has no label'); 59 | } 60 | 61 | return $this->labels; 62 | } 63 | 64 | public function addLabels(array $labels) 65 | { 66 | $this->labels = $labels; 67 | } 68 | 69 | public function getLabel() 70 | { 71 | if (!empty($this->labels)) { 72 | return $this->labels[0]; 73 | } 74 | 75 | throw new \RuntimeException('The node has no label'); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Graph/Relationship.php: -------------------------------------------------------------------------------- 1 | type; 40 | } 41 | 42 | /** 43 | * @param $type 44 | */ 45 | public function setType($type) 46 | { 47 | $this->type = (string) $type; 48 | } 49 | 50 | /** 51 | * @return mixed 52 | */ 53 | public function getSourceId() 54 | { 55 | return $this->sourceId; 56 | } 57 | 58 | /** 59 | * @return mixed 60 | */ 61 | public function getTargetId() 62 | { 63 | return $this->targetId; 64 | } 65 | 66 | /** 67 | * @param $v 68 | */ 69 | public function setSourceId($v) 70 | { 71 | $this->sourceId = (string) $v; 72 | } 73 | 74 | /** 75 | * @param $v 76 | */ 77 | public function setTargetId($v) 78 | { 79 | $this->targetId = (string) $v; 80 | } 81 | 82 | /** 83 | * @return mixed 84 | */ 85 | public function getSourceLabel() 86 | { 87 | return $this->sourceLabel; 88 | } 89 | 90 | /** 91 | * @return mixed 92 | */ 93 | public function getTargetLabel() 94 | { 95 | return $this->targetLabel; 96 | } 97 | 98 | /** 99 | * @param $v 100 | */ 101 | public function setSourceLabel($v) 102 | { 103 | $this->sourceLabel = (string) $v; 104 | } 105 | 106 | /** 107 | * @param $v 108 | */ 109 | public function setTargetLabel($v) 110 | { 111 | $this->targetLabel = (string) $v; 112 | } 113 | 114 | public function getProperties() 115 | { 116 | return $this->properties; 117 | } 118 | 119 | public function addProperty($name, $v) 120 | { 121 | $this->properties[$name] = $v; 122 | } 123 | 124 | public function hasProperties() 125 | { 126 | if (!empty($this->properties)) { 127 | return true; 128 | } 129 | 130 | return false; 131 | } 132 | 133 | public function getProperty($key) 134 | { 135 | if (array_key_exists($key, $this->properties)) { 136 | return $this->properties[$key]; 137 | } 138 | 139 | return null; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/GraphGenerator/Generator.php: -------------------------------------------------------------------------------- 1 | graphProcessor = $graphProcessor; 15 | } 16 | 17 | public function generateGraph(GraphSchema $graphSchema) 18 | { 19 | /** 20 | * <-- Here should come the model manager adding properties to the schema 21 | */ 22 | 23 | $graph = $this->graphProcessor->process($graphSchema); 24 | return $graph; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Helper/ArrayUtils.php: -------------------------------------------------------------------------------- 1 | $v) { 10 | if ("" === $v) { 11 | unset($array[$k]); 12 | } 13 | } 14 | 15 | return $array; 16 | } 17 | } -------------------------------------------------------------------------------- /src/Helper/CypherHelper.php: -------------------------------------------------------------------------------- 1 | value, should be used 66 | * between the "openNodePropertiesBracket" and "closeNodePropertiesBracket" methods 67 | * 68 | * @param $key 69 | * @param $value 70 | * @return string 71 | */ 72 | public function addNodeProperty($key, $value) 73 | { 74 | if (is_string($value)) { 75 | $value = '"'.htmlentities($value, ENT_QUOTES, 'UTF-8').'"'; 76 | } elseif (is_int($value)) { 77 | $value = 'toInt('.$value.')'; 78 | } 79 | 80 | return $key.':'.$value; 81 | } 82 | 83 | /** 84 | * Add a relationship path 85 | * First it try to merge nodes, id's are taken from the already node generated ids 86 | * 87 | * Trying to MERGE the nodes could add payload to the query, but as depending of the amount 88 | * of nodes and relationships creations, the queries may be splitted in multiple statements 89 | * to avoid dealing with too large bodies in http requests, that's why we first need to 90 | * get the start and end nodes 91 | * 92 | * @param $start 93 | * @param $end 94 | * @param $type 95 | * @param array $properties 96 | * @return string 97 | */ 98 | public function addRelationship($start, $end, $type, array $properties = array()) 99 | { 100 | $sa = 'r'.sha1($start.microtime()); 101 | $es = 'r'.sha1($end.microtime()); 102 | $q = 'MERGE ('.$sa.' { neogen_id: "'.$start.'" }) '; 103 | $q .= 'MERGE ('.$es.' { neogen_id: "'.$end.'" }) '; 104 | 105 | if (!empty($properties)) { 106 | $props = ' { '; 107 | $i = 0; 108 | $max = count($properties); 109 | foreach ($properties as $key => $value) { 110 | if (is_string($value)) { 111 | $val = '"'.htmlentities($value, ENT_QUOTES, 'UTF-8').'"'; 112 | } elseif (is_int($value)) { 113 | $val = 'toInt('.$value.')'; 114 | } elseif (is_float($value)) { 115 | $val = 'toFloat('.$value.')'; 116 | } else { 117 | $val = htmlentities($value); 118 | } 119 | $props .= $key.':'.$val; 120 | if ($i < $max-1) { 121 | $props .= ','; 122 | } 123 | 124 | $i++; 125 | } 126 | 127 | $props .= '}'; 128 | } else { 129 | $props = ''; 130 | } 131 | 132 | $q .= 'MERGE ('.$sa.')-[:'.$type.$props.']->('.$es.') '; 133 | if (!empty($props)) { 134 | //print_r($q); 135 | //exit(); 136 | } 137 | 138 | return $q; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/ModelLayer/ModelLayerHandler.php: -------------------------------------------------------------------------------- 1 | models = []; 19 | $this->finder = new Finder(); 20 | $this->resourcesPath = null !== $resourcesPath ? $resourcesPath : __DIR__.'/../Resources/models'; 21 | } 22 | 23 | public function findModelResources() 24 | { 25 | $files = $this->finder->files()->name('*.yml')->in($this->resourcesPath); 26 | 27 | foreach ($files as $file) { 28 | $definitions = Yaml::parse($file); 29 | foreach ($definitions as $key => $definition) { 30 | $this->models[$key] = $definition; 31 | } 32 | } 33 | } 34 | 35 | public function getModels() 36 | { 37 | return $this->models; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Neogen.php: -------------------------------------------------------------------------------- 1 | serviceContainer = $container; 38 | 39 | return $this; 40 | } 41 | 42 | /** 43 | * Creates a new instance of Neogen 44 | * 45 | * @return Neogen 46 | */ 47 | public static function create() 48 | { 49 | return new self(); 50 | } 51 | 52 | /** 53 | * Returns the Neogen library's version 54 | * 55 | * @return string Neogen version 56 | */ 57 | public static function getVersion() 58 | { 59 | return self::$version; 60 | } 61 | 62 | /** 63 | * @return \Neoxygen\Neogen\Neogen 64 | */ 65 | public function build() 66 | { 67 | $extension = new NeogenExtension(); 68 | $this->serviceContainer->registerExtension($extension); 69 | $this->serviceContainer->loadFromExtension($extension->getAlias(), $this->getConfiguration()); 70 | $this->serviceContainer->compile(); 71 | $this->getParserManager()->registerParser(new YamlFileParser()); 72 | $this->getParserManager()->registerParser(new CypherPattern()); 73 | 74 | return $this; 75 | } 76 | 77 | /** 78 | * Generates a graph based on a given user schema array 79 | * 80 | * @param array $userSchema 81 | * @return Graph\Graph 82 | */ 83 | public function generateGraph(array $userSchema) 84 | { 85 | $graphSchema = $this->getService('neogen.schema_builder')->buildGraph($userSchema); 86 | 87 | return $this->getGraphGenerator()->generateGraph($graphSchema); 88 | } 89 | 90 | /** 91 | * @return ContainerBuilder|ContainerInterface 92 | */ 93 | public function getServiceContainer() 94 | { 95 | return $this->serviceContainer; 96 | } 97 | 98 | /** 99 | * Returns the parser manager service 100 | * 101 | * @return \Neoxygen\Neogen\Parser\ParserManager 102 | */ 103 | public function getParserManager() 104 | { 105 | return $this->getService('neogen.parser_manager'); 106 | } 107 | 108 | /** 109 | * @param string $parser 110 | * @return Parser\ParserInterface 111 | * @throws Exception\ParserNotFoundException 112 | */ 113 | public function getParser($parser) 114 | { 115 | return $this->getParserManager()->getParser($parser); 116 | } 117 | 118 | /** 119 | * Returns the graph serializer service 120 | * 121 | * @return \Neoxygen\Neogen\Util\GraphSerializer 122 | */ 123 | public function getGraphSerializer() 124 | { 125 | return $this->getService('neogen.graph_serializer'); 126 | } 127 | 128 | /** 129 | * Returns the graph generator service 130 | * 131 | * @return \Neoxygen\Neogen\GraphGenerator\Generator 132 | */ 133 | public function getGraphGenerator() 134 | { 135 | return $this->getService('neogen.graph_generator'); 136 | } 137 | 138 | /** 139 | * @param $id The service id 140 | * @return object 141 | */ 142 | private function getService($id) 143 | { 144 | if (!$this->serviceContainer->isFrozen()) { 145 | throw new \RuntimeException(sprintf('The Service "%s" can not be accessed. Maybe you forgot to call the "build" method?', $id)); 146 | } 147 | 148 | return $this->serviceContainer->get($id); 149 | } 150 | 151 | /** 152 | * Return the current configuration 153 | * 154 | * @return array 155 | */ 156 | private function getConfiguration() 157 | { 158 | return $this->configuration; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/Parser/CypherDefinition.php: -------------------------------------------------------------------------------- 1 | parts; 19 | } 20 | 21 | /** 22 | * @return mixed 23 | */ 24 | public function getNodes() 25 | { 26 | return $this->nodes; 27 | } 28 | 29 | /** 30 | * @return mixed 31 | */ 32 | public function getEdges() 33 | { 34 | return $this->edges; 35 | } 36 | 37 | 38 | } -------------------------------------------------------------------------------- /src/Parser/CypherPattern.php: -------------------------------------------------------------------------------- 1 | ?-\[)(?::)([_\w\d]+)(\s?{[-,:~\'\"{}\[\]\s\w\d]+})?(\s?\*[\w\d+]\.\.[\w\d])(\]-?)/'; 24 | 25 | /** 26 | * 27 | */ 28 | const SPLIT_PATTERN = "/((?:?)(?:\\[[^<^>.]*\\*[a-z0-9]+\\.\\.[a-z0-9]+\\])(?:?))/"; 29 | 30 | /** 31 | * 32 | */ 33 | const PROPERTY_KEY_VALIDATION_PATTERN = "/^[!\\?]?[a-z]+_?[a-z0-9]*$/"; 34 | 35 | /** 36 | * 37 | */ 38 | const INGOING_RELATIONSHIP = 'IN'; 39 | 40 | /** 41 | * 42 | */ 43 | const OUTGOING_RELATIONSHIP = 'OUT'; 44 | 45 | /** 46 | * @var array 47 | */ 48 | private $nodes; 49 | 50 | /** 51 | * @var array 52 | */ 53 | private $edges; 54 | 55 | /** 56 | * @var array 57 | */ 58 | private $labels; 59 | 60 | /** 61 | * @var array 62 | */ 63 | private $identifiers; 64 | 65 | /** 66 | * @var array 67 | */ 68 | private $nodeInfoMap; 69 | 70 | /** 71 | * @param string $cypherPattern 72 | * @return array The converted Cypher => array schema 73 | * @throws CypherPatternException When parse exception 74 | */ 75 | public function parse($cypherPattern) 76 | { 77 | $this->nodes = []; 78 | $this->edges = []; 79 | $this->labels = []; 80 | $this->identifiers = []; 81 | $this->nodeInfoMap = []; 82 | $lines = explode("\n", $this->preFormatPattern($cypherPattern)); 83 | 84 | foreach ($lines as $line) { 85 | $parts = $this->parseLine($line); 86 | foreach ($parts as $key => $part) { 87 | if (preg_match(self::NODE_PATTERN, $part, $output)) { 88 | $nodeInfo = $this->getNodePatternInfo($output, $part); 89 | $this->processNode($nodeInfo, $part); 90 | } elseif (preg_match(self::EDGE_PATTERN, $part, $output)) { 91 | $edgeInfo = $this->getEdgePatternInfo($output); 92 | $this->processEdge($edgeInfo, $key, $parts); 93 | } else { 94 | throw new CypherPatternException(sprintf('The part "%s" could not be parsed, check it for type errors.', $part)); 95 | } 96 | } 97 | } 98 | 99 | return $this->getSchema(); 100 | } 101 | 102 | /** 103 | * @param $pattern 104 | * @return mixed 105 | */ 106 | public function preFormatPattern($pattern) 107 | { 108 | $lines = explode("\n", $pattern); 109 | $paste = ''; 110 | foreach ($lines as $line) { 111 | $l = trim($line); 112 | if (false === strpos($l, '//')) { 113 | $paste .= $l; 114 | } 115 | } 116 | $formatInLines = str_replace(')(',")\n(", $paste); 117 | 118 | return $formatInLines; 119 | } 120 | 121 | /** 122 | * @param $cypherPattern 123 | * @return array 124 | */ 125 | public function splitLineBreaks($cypherPattern) 126 | { 127 | $lines = explode("\n", $cypherPattern); 128 | $parsedLines = []; 129 | foreach ($lines as $line) { 130 | if (false === strpos($line, '//')) { 131 | $parsedLines[] = trim(htmlspecialchars_decode($line)); 132 | } 133 | } 134 | 135 | return $parsedLines; 136 | } 137 | 138 | /** 139 | * @param $cypherLineText 140 | * @return array 141 | */ 142 | public function parseLine($cypherLineText) 143 | { 144 | $parts = preg_split(self::SPLIT_PATTERN, $cypherLineText, null, PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE); 145 | 146 | return $parts; 147 | } 148 | 149 | public function matchPattern($pattern) 150 | { 151 | if (preg_match(self::NODE_PATTERN, $pattern, $output)) { 152 | return $this->getNodePatternDefintion($output, $pattern); 153 | } elseif (preg_match(self::EDGE_PATTERN, $pattern, $output)) { 154 | // 155 | } 156 | 157 | throw new ParseException(sprintf('Unable to parse part "%s"', $pattern)); 158 | 159 | } 160 | 161 | public function getNodePatternDefintion(array $pregMatchOutput, $patternPart) 162 | { 163 | if (!array_key_exists(1, $pregMatchOutput) || empty($pregMatchOutput[1])) { 164 | throw new ParseException(sprintf('A node identifier is mandatory, none given in "%s"', $patternPart)); 165 | } 166 | 167 | $identifier = trim((string) $pregMatchOutput[1]); 168 | 169 | if (!array_key_exists(2, $pregMatchOutput) || empty($pregMatchOutput[2])) { 170 | throw new ParseException(sprintf('At least one label is required in the pattern, none given in "%s"', $patternPart)); 171 | } 172 | 173 | $defintion = new NodeDefinition($identifier); 174 | 175 | $labels = ArrayUtils::cleanEmptyStrings(explode(':', trim($pregMatchOutput[2]))); 176 | foreach ($labels as $k => $label) { 177 | $label = trim($label); 178 | $model = null; 179 | if (0 === strpos($label, '#')) { 180 | $label = substr($label, 1); 181 | $model = $label; 182 | } 183 | 184 | $defintion->addLabel($label); 185 | $defintion->addModel($model); 186 | } 187 | 188 | if (array_key_exists(3, $pregMatchOutput)) { 189 | $properties = Yaml::parse(trim($pregMatchOutput[3])); 190 | foreach ($properties as $k => $v) { 191 | if (!preg_match(self::PROPERTY_KEY_VALIDATION_PATTERN, $k, $out)) { 192 | throw new ParseException(sprintf('The property key "%s" is not valid in part "%s"', $k, $patternPart)); 193 | } 194 | 195 | if ($defintion->hasProperty($k)) { 196 | throw new ParseException(sprintf('The property key "%s" can only be defined once in part "%s"', $k, $patternPart)); 197 | } 198 | 199 | $defintion->addProperty($this->getPropertyDefinition($k, $v)); 200 | } 201 | } 202 | 203 | return $defintion; 204 | } 205 | 206 | public function getPropertyDefinition($key, $generator) 207 | { 208 | $u = false; 209 | $i = false; 210 | if (0 === strpos($key, '!')) { 211 | $u = true; 212 | $key = substr($key, 1); 213 | } elseif (0 === strpos($key, '?')) { 214 | $i = true; 215 | $key = substr($key, 1); 216 | } 217 | 218 | return new PropertyDefinition($key, $generator, $i, $u); 219 | } 220 | 221 | public function getEdgePatternDefinition(array $pregMatchOutput, $patternPart) 222 | { 223 | 224 | } 225 | 226 | /** 227 | * @param array $nodeInfo 228 | * @param null $part 229 | * @throws CypherPatternException 230 | */ 231 | public function processNode(array $nodeInfo, $part = null) 232 | { 233 | $identifier = $nodeInfo['identifier']; 234 | if (array_key_exists($identifier, $this->nodes)) { 235 | return; 236 | } 237 | if (empty($nodeInfo['labels']) && !array_key_exists($nodeInfo['identifier'], $this->nodes)) { 238 | throw new CypherPatternException(sprintf('The identifier "%s" has not been declared in "%s"', $nodeInfo['identifier'], $part)); 239 | } 240 | 241 | $labels = $nodeInfo['labels']; 242 | 243 | $node = [ 244 | 'identifier' => $identifier, 245 | 'labels' => $labels, 246 | 'count' => $nodeInfo['count'], 247 | 'properties' => $nodeInfo['properties'], 248 | 'models' => $nodeInfo['models'] 249 | ]; 250 | if (null !== $nodeInfo['identifier']) { 251 | $this->identifiers[$nodeInfo['identifier']] = $identifier; 252 | if (null !== $identifier) { 253 | $virtualPart = '('.$nodeInfo['identifier'].')'; 254 | $this->nodeInfoMap[$virtualPart] = $nodeInfo; 255 | } 256 | 257 | if (null !== $part && null !== $nodeInfo['identifier']) { 258 | $this->nodeInfoMap[trim($part)] = $nodeInfo; 259 | } 260 | } 261 | 262 | if ($nodeInfo['properties']) { 263 | 264 | try { 265 | $properties = Yaml::parse($node['properties']); 266 | if (null !== $properties) { 267 | foreach ($properties as $key => $type) { 268 | if (is_array($type)) { 269 | $props[$key]['type'] = key($type); 270 | $props[$key]['params'] = []; 271 | foreach (current($type) as $k => $v) { 272 | $props[$key]['params'][] = $v; 273 | } 274 | } else { 275 | $props[$key] = $type; 276 | } 277 | } 278 | $node['properties'] = $props; 279 | } 280 | 281 | } catch (ParseException $e) { 282 | throw new CypherPatternException(sprintf('Malformed inline properties near "%s"', $node['properties'])); 283 | } 284 | } 285 | 286 | $this->nodes[$identifier] = $node; 287 | $this->labels[] = $labels; 288 | } 289 | 290 | /** 291 | * @param array $edgeInfo 292 | * @param $key 293 | * @param array $parts 294 | * @throws CypherPatternException 295 | */ 296 | public function processEdge(array $edgeInfo, $key, array $parts) 297 | { 298 | $prev = trim($parts[$key-1]); 299 | $previous = $this->nodeInfoMap[trim($prev)]; 300 | $prevNode = $previous['identifier']; 301 | 302 | $next = trim($parts[$key+1]); 303 | 304 | preg_match(self::NODE_PATTERN, $next, $output); 305 | $info = $this->getNodePatternInfo($output, $next); 306 | $this->processNode($info); 307 | 308 | $nextious = $this->nodeInfoMap[trim($next)]; 309 | $nextNode = !empty($nextious['identifier']) ? $nextious['identifier'] : $this->identifiers[$nextious['identifier']]; 310 | 311 | $start = 'OUT' === $edgeInfo['direction'] ? $prevNode : $nextNode; 312 | $end = 'OUT' === $edgeInfo['direction'] ? $nextNode : $prevNode; 313 | 314 | $edge = [ 315 | 'start' => $start, 316 | 'end' => $end, 317 | 'type' => $edgeInfo['type'], 318 | 'mode' => $edgeInfo['cardinality'], 319 | ]; 320 | 321 | try { 322 | $props = []; 323 | $properties = Yaml::parse($edgeInfo['properties']); 324 | if (null !== $properties) { 325 | foreach ($properties as $key => $type) { 326 | if (is_array($type)) { 327 | $props[$key]['type'] = key($type); 328 | $props[$key]['params'] = []; 329 | foreach (current($type) as $k => $v) { 330 | $props[$key]['params'][] = $v; 331 | } 332 | } else { 333 | $props[$key] = $type; 334 | } 335 | } 336 | } 337 | $edge['properties'] = $props; 338 | } catch (ParseException $e) { 339 | throw new CypherPatternException(sprintf('Malformed inline properties near "%s"', $edgeInfo['properties'])); 340 | } 341 | 342 | $this->edges[] = $edge; 343 | 344 | } 345 | 346 | /** 347 | * @param $identifier 348 | * @return bool 349 | */ 350 | public function hasIdentifier($identifier) 351 | { 352 | return array_key_exists($identifier, $this->identifiers); 353 | } 354 | 355 | /** 356 | * @param array $nodePattern 357 | * @param $part 358 | * @return array 359 | * @throws CypherPatternException 360 | */ 361 | public function getNodePatternInfo(array $nodePattern, $part) 362 | { 363 | if (empty($nodePattern[3])) { 364 | throw new CypherPatternException(sprintf('An identifier must be defined for nodes in "%s"', $part)); 365 | } 366 | 367 | $labels = explode(':', trim($nodePattern['4'])); 368 | array_shift($labels); 369 | $models = []; 370 | $lbls = []; 371 | foreach ($labels as $lbl) { 372 | $pos = strpos($lbl, '#'); 373 | if ($pos !== false && 0 === $pos) { 374 | $sanitized = str_replace('#', '', $lbl); 375 | $models[] = $sanitized; 376 | $lbls[] = $sanitized; 377 | } else { 378 | $lbls[] = $lbl; 379 | } 380 | } 381 | $defaultInfo = [ 382 | 'identifier' => $this->nullString($nodePattern[3]), 383 | 'labels' => $lbls, 384 | 'properties' => $this->nullString($nodePattern[5]), 385 | 'count' => $this->nullString($nodePattern[6]), 386 | 'models' => $models 387 | ]; 388 | if (empty($defaultInfo['count']) || '' == $defaultInfo['count']) { 389 | $defaultInfo['count'] = 1; 390 | } 391 | 392 | $this->nodeInfoMap[trim($part)] = $defaultInfo; 393 | 394 | return $defaultInfo; 395 | } 396 | 397 | /** 398 | * @param array $edgePattern 399 | * @return array 400 | * @throws CypherPatternException 401 | */ 402 | public function getEdgePatternInfo(array $edgePattern) 403 | { 404 | $arrowStart = $edgePattern[1]; 405 | $arrowEnd = $edgePattern[5]; 406 | $direction = $this->detectEdgeDirection($arrowStart, $arrowEnd); 407 | if (null === $direction) { 408 | $edgePart = &$edgePattern; 409 | unset($edgePart[0]); 410 | $patt = implode('', $edgePart); 411 | throw new CypherPatternException(sprintf('The direction of the relationship must be defined, near "%s".', $patt)); 412 | } 413 | $type = $this->nullString($edgePattern[2]); 414 | if (null === $type) { 415 | throw new CypherPatternException(sprintf('The type of the relationship must be defined, near "%s"', $edgePattern)); 416 | } 417 | $edge = [ 418 | 'type' => $type, 419 | 'direction' => $direction, 420 | 'cardinality' => $this->checkCardinality($edgePattern), 421 | 'properties' => $this->nullString($edgePattern[3]) 422 | ]; 423 | 424 | return $edge; 425 | 426 | } 427 | 428 | /** 429 | * @return string 430 | */ 431 | public function getNodePattern() 432 | { 433 | return self::NODE_PATTERN; 434 | } 435 | 436 | /** 437 | * @return string 438 | */ 439 | public function getEdgePattern() 440 | { 441 | return self::EDGE_PATTERN; 442 | } 443 | 444 | /** 445 | * @param $pattern 446 | * @return mixed 447 | * @throws CypherPatternException 448 | */ 449 | public function checkCardinality($pattern) 450 | { 451 | $allowedCardinalities = ['1..n', 'n..n', '1..1', 'n..1']; 452 | $cardinality = str_replace('*', '', trim($pattern[4])); 453 | if (!in_array($cardinality, $allowedCardinalities)) { 454 | throw new CypherPatternException(sprintf('The cardinality "%s" is not allowed', $cardinality)); 455 | } 456 | 457 | return $cardinality; 458 | } 459 | 460 | /** 461 | * @return array 462 | */ 463 | public function getSchema() 464 | { 465 | return array( 466 | 'nodes' => $this->nodes, 467 | 'relationships' => $this->edges 468 | ); 469 | } 470 | 471 | /** 472 | * @return string 473 | */ 474 | public function getName() 475 | { 476 | return 'cypher'; 477 | } 478 | 479 | /** 480 | * @param $start 481 | * @param $end 482 | * @return null|string 483 | */ 484 | private function detectEdgeDirection($start, $end) 485 | { 486 | if ($start === '-[' && $end === ']->') { 487 | return self::OUTGOING_RELATIONSHIP; 488 | } elseif ($start === '<-[' && $end === ']-') { 489 | return self::INGOING_RELATIONSHIP; 490 | } 491 | 492 | return null; 493 | } 494 | 495 | /** 496 | * @param $string 497 | * @return mixed|null 498 | */ 499 | private function nullString($string) 500 | { 501 | if (trim($string) === '') { 502 | return null; 503 | } 504 | 505 | return str_replace('*', '', trim($string)); 506 | } 507 | } 508 | -------------------------------------------------------------------------------- /src/Parser/Definition/GraphDefinition.php: -------------------------------------------------------------------------------- 1 | identifier = (string) $identifier; 33 | } 34 | 35 | /** 36 | * @param string $label 37 | */ 38 | public function addLabel($label) 39 | { 40 | $this->labels[] = $label; 41 | } 42 | 43 | /** 44 | * @param string $label 45 | * @return bool 46 | */ 47 | public function hasLabel($label) 48 | { 49 | return in_array($label, $this->labels); 50 | } 51 | 52 | /** 53 | * @param string $model 54 | */ 55 | public function addModel($model) 56 | { 57 | if (null === $model) { return; } 58 | $this->models[] = $model; 59 | } 60 | 61 | /** 62 | * @param \GraphAware\Neogen\Parser\Definition\PropertyDefinition $propertyDefinition 63 | */ 64 | public function addProperty(PropertyDefinition $propertyDefinition) 65 | { 66 | $this->properties[] = $propertyDefinition; 67 | } 68 | 69 | /** 70 | * @param string $key 71 | * @return bool 72 | */ 73 | public function hasProperty($key) 74 | { 75 | foreach ($this->properties as $property) { 76 | if ($key === $property->getKey()) { 77 | return true; 78 | } 79 | } 80 | 81 | return false; 82 | } 83 | 84 | /** 85 | * @return string 86 | */ 87 | public function getIdentifier() 88 | { 89 | return $this->identifier; 90 | } 91 | 92 | /** 93 | * @return array 94 | */ 95 | public function getLabels() 96 | { 97 | return $this->labels; 98 | } 99 | 100 | /** 101 | * @return array 102 | */ 103 | public function getModels() 104 | { 105 | return $this->models; 106 | } 107 | 108 | /** 109 | * @return \GraphAware\Neogen\Parser\Definition\PropertyDefinition[] 110 | */ 111 | public function getProperties() 112 | { 113 | return $this->properties; 114 | } 115 | 116 | 117 | } -------------------------------------------------------------------------------- /src/Parser/Definition/PropertyDefinition.php: -------------------------------------------------------------------------------- 1 | key = $key; 18 | $this->generator = $generator; 19 | $this->indexed = $indexed; 20 | $this->unique = $unique; 21 | } 22 | 23 | /** 24 | * @return mixed 25 | */ 26 | public function getKey() 27 | { 28 | return $this->key; 29 | } 30 | 31 | /** 32 | * @return mixed 33 | */ 34 | public function getGenerator() 35 | { 36 | return $this->generator; 37 | } 38 | 39 | public function isIndexed() 40 | { 41 | return $this->indexed; 42 | } 43 | 44 | public function isUnique() 45 | { 46 | return $this->unique; 47 | } 48 | } -------------------------------------------------------------------------------- /src/Parser/ParserInterface.php: -------------------------------------------------------------------------------- 1 | parsers; 22 | } 23 | 24 | /** 25 | * Register a new schema definition parser 26 | * 27 | * @param \Neoxygen\Neogen\Parser\ParserInterface $parser 28 | */ 29 | public function registerParser(ParserInterface $parser) 30 | { 31 | $this->parsers[$parser->getName()] = $parser; 32 | } 33 | 34 | /** 35 | * Checks whether or not at least one parser exist 36 | * 37 | * @return bool 38 | */ 39 | public function hasParsers() 40 | { 41 | if (!empty($this->parsers)) { 42 | return true; 43 | } 44 | 45 | return false; 46 | } 47 | 48 | /** 49 | * Checks whether or not the parser with the name value exist 50 | * 51 | * @param $name 52 | * @return bool 53 | */ 54 | public function hasParser($name) 55 | { 56 | if (array_key_exists($name, $this->parsers)) { 57 | return true; 58 | } 59 | 60 | return false; 61 | } 62 | 63 | /** 64 | * Returns the schema definition parser for the name value 65 | * 66 | * @param $name 67 | * @return \Neoxygen\Neogen\Parser\ParserInterface 68 | * @throws ParserNotFoundException 69 | */ 70 | public function getParser($name) 71 | { 72 | if (!$this->hasParser($name)) { 73 | throw new ParserNotFoundException(sprintf('The parser with NAME "%s" is not registered', $name)); 74 | } 75 | 76 | return $this->parsers[$name]; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Parser/YamlFileParser.php: -------------------------------------------------------------------------------- 1 | fs = new Filesystem(); 24 | } 25 | 26 | /** 27 | * @param $schemaFilePath The User Schema File Path 28 | * @return array 29 | * @throws SchemaDefinitionException 30 | */ 31 | public function parse($schemaFilePath) 32 | { 33 | $schema = $this->getSchemaFileContent($schemaFilePath); 34 | 35 | return $schema; 36 | } 37 | 38 | /** 39 | * Returns the name of the parser 40 | * 41 | * @return string 42 | */ 43 | public function getName() 44 | { 45 | return 'yaml'; 46 | } 47 | 48 | /** 49 | * Returns the FileSystem component 50 | * 51 | * @return Filesystem 52 | */ 53 | public function getFS() 54 | { 55 | return $this->fs; 56 | } 57 | 58 | /** 59 | * Get the contents of the User Schema YAML File and transforms it to php array 60 | * 61 | * @param $filePath 62 | * @return array 63 | * @throws SchemaDefinitionException 64 | */ 65 | public function getSchemaFileContent($filePath) 66 | { 67 | if (!$this->fs->exists($filePath)) { 68 | throw new SchemaDefinitionException(sprintf('The schema file "%s" was not found', $filePath)); 69 | } 70 | 71 | $content = file_get_contents($filePath); 72 | 73 | try { 74 | $schema = Yaml::parse($content); 75 | 76 | return $schema; 77 | } catch (ParseException $e) { 78 | throw new SchemaDefinitionException($e->getMessage()); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Processor/GraphProcessor.php: -------------------------------------------------------------------------------- 1 | faker = $faker; 43 | } 44 | 45 | /** 46 | * @param GraphSchema $schema 47 | * @param null|int $seed 48 | * @return Graph 49 | */ 50 | public function process(GraphSchema $schema, $seed = null) 51 | { 52 | $this->nodesByIdentifier = new ObjectCollection(); 53 | $this->nodes = new ObjectCollection(); 54 | $this->relationships = new ObjectCollection(); 55 | 56 | foreach ($schema->getNodes() as $node) { 57 | $this->processNodeDefinition($node, $seed); 58 | } 59 | 60 | foreach ($schema->getRelationships() as $relationship) { 61 | $this->processRelationshipDefinition($relationship, $seed); 62 | } 63 | 64 | $graph = new Graph(); 65 | $graph->setNodes($this->nodes); 66 | $graph->setEdges($this->relationships); 67 | $graph->setSchema($schema); 68 | 69 | return $graph; 70 | } 71 | 72 | /** 73 | * @param NodeDefinition $node 74 | * @param null|int $seed 75 | * @return ObjectCollection 76 | */ 77 | public function processNodeDefinition(NodeDefinition $node, $seed) 78 | { 79 | $collection = new ObjectCollection(); 80 | for ($i=0; $i < $node->getAmount(); $i++) { 81 | $id = $this->faker->generate('uuid', [], null, true); 82 | $n = new Node($id); 83 | $n->addLabels($node->getLabels()->toArray()); 84 | foreach ($node->getProperties() as $property) { 85 | $n->addProperty($property->getName(), $this->getFakeData($property, $seed)); 86 | } 87 | $collection->add($n); 88 | $this->nodes->add($n); 89 | } 90 | 91 | $this->nodesByIdentifier->set($node->getIdentifier(), $collection); 92 | } 93 | 94 | /** 95 | * @param RelationshipDefinition $relationship 96 | * @param null|int $seed 97 | */ 98 | public function processRelationshipDefinition(RelationshipDefinition $relationship, $seed) 99 | { 100 | switch ($relationship->getCardinality()) { 101 | case 'n..1': 102 | $this->processNTo1Relationship($relationship, $seed); 103 | break; 104 | case '1..1': 105 | $this->process1To1Relationship($relationship, $seed); 106 | break; 107 | case 'n..n': 108 | $this->processNToNRelationship($relationship, $seed); 109 | break; 110 | case '1..n': 111 | $this->process1ToNRelationship($relationship, $seed); 112 | break; 113 | default: 114 | throw new \RuntimeException(sprintf('Unable to process relationship with "%s" cardinality', $relationship->getCardinality())); 115 | } 116 | } 117 | 118 | /** 119 | * @param RelationshipDefinition $relationship 120 | * @param null|int $seed 121 | */ 122 | public function processNTo1Relationship(RelationshipDefinition $relationship, $seed) 123 | { 124 | foreach ($this->nodesByIdentifier[$relationship->getStartNode()] as $startNode) { 125 | $endNode = $this->getRandomNode($this->nodesByIdentifier[$relationship->getEndNode()]); 126 | $this->createRelationship($relationship->getType(), 127 | $startNode->getId(), 128 | $startNode->getLabel(), 129 | $endNode->getId(), 130 | $endNode->getLabel(), 131 | $relationship->getProperties(), 132 | $seed); 133 | } 134 | 135 | } 136 | 137 | /** 138 | * @param RelationshipDefinition $relationship 139 | * @param null|int $seed 140 | */ 141 | public function process1To1Relationship(RelationshipDefinition $relationship, $seed) 142 | { 143 | $usedEnds = []; 144 | $startNodes = $this->nodesByIdentifier[$relationship->getStartNode()]; 145 | $endNodes = $this->nodesByIdentifier[$relationship->getEndNode()]; 146 | $maxEnds = $endNodes->count(); 147 | $i = 0; 148 | foreach ($startNodes as $startNode) { 149 | if (!in_array($i, $usedEnds)) { 150 | $endPosition = $this->getNotUsedNodePosition($usedEnds, $endNodes, $startNode); 151 | if (null !== $endPosition) { 152 | $endNode = $endNodes->get($endPosition); 153 | $this->createRelationship( 154 | $relationship->getType(), 155 | $startNode->getId(), 156 | $startNode->getLabel(), 157 | $endNode->getId(), 158 | $endNode->getLabel(), 159 | $relationship->getProperties(), 160 | $seed 161 | ); 162 | $usedEnds[] = $endPosition; 163 | $usedEnds[] = $i; 164 | } 165 | } 166 | $i++; 167 | } 168 | } 169 | 170 | /** 171 | * @param RelationshipDefinition $relationship 172 | * @param null|int $seed 173 | */ 174 | public function processNToNRelationship(RelationshipDefinition $relationship, $seed) 175 | { 176 | $targetCount = $this->getTargetNodesCount($relationship, $this->nodesByIdentifier[$relationship->getEndNode()]); 177 | $startNodes = $this->nodesByIdentifier[$relationship->getStartNode()]; 178 | $endNodes = $this->nodesByIdentifier[$relationship->getEndNode()]; 179 | foreach ($startNodes as $i => $startNode) { 180 | $usedTargets = []; 181 | $x = 0; 182 | while ($x < $targetCount) { 183 | $endNodePosition = $this->getNotUsedNodePosition($usedTargets, $endNodes, $startNode); 184 | $endNode = $endNodes->get($endNodePosition); 185 | $this->createRelationship( 186 | $relationship->getType(), 187 | $startNode->getId(), 188 | $startNode->getLabel(), 189 | $endNode->getId(), 190 | $endNode->getLabel(), 191 | $relationship->getProperties(), 192 | $seed 193 | ); 194 | $usedTargets[$endNodePosition] = null; 195 | $x++; 196 | } 197 | } 198 | } 199 | 200 | /** 201 | * @param RelationshipDefinition $relationship 202 | * @param null|int $seed 203 | */ 204 | public function process1ToNRelationship(RelationshipDefinition $relationship, $seed) 205 | { 206 | $startNodes = $this->nodesByIdentifier[$relationship->getStartNode()]; 207 | $endNodes = $this->nodesByIdentifier[$relationship->getEndNode()]; 208 | $target = $this->calculateApproxTargetNodes($startNodes->count(), $endNodes->count()); 209 | $maxIteration = 1 === $target ? $startNodes->count() : $startNodes->count() -1; 210 | if ($endNodes->count() > $startNodes->count()) { 211 | if ($endNodes->count() % $startNodes->count() === 0) { 212 | $maxIteration = $startNodes->count(); 213 | } 214 | } 215 | $ec = $endNodes->count(); 216 | $eci = 0; 217 | $ssi = 0; 218 | for ($s = 0; $s < $maxIteration; $s++) { 219 | for ($i = 0; $i < $target; $i++) { 220 | $startNode = $startNodes->get($s); 221 | $endNode = $endNodes->get($eci); 222 | $this->createRelationship( 223 | $relationship->getType(), 224 | $startNode->getId(), 225 | $startNode->getLabel(), 226 | $endNode->getId(), 227 | $endNode->getLabel(), 228 | $relationship->getProperties(), 229 | $seed 230 | ); 231 | $eci++; 232 | } 233 | $ssi++; 234 | } 235 | if ($ssi < $startNodes->count()) { 236 | $lastStartNode = $startNodes->get($ssi); 237 | for ($eci; $eci < $ec; $eci++) { 238 | $endNode = $endNodes->get($eci); 239 | $this->createRelationship( 240 | $relationship->getType(), 241 | $lastStartNode->getId(), 242 | $lastStartNode->getLabel(), 243 | $endNode->getId(), 244 | $endNode->getLabel(), 245 | $relationship->getProperties(), 246 | $seed 247 | ); 248 | } 249 | } 250 | } 251 | 252 | /** 253 | * 254 | * Guess the number of nodes to be associated to 1 node 255 | * 256 | * @param int $startCount The count of relationship start nodes 257 | * @param int $endCount The count of relationship end nodes 258 | * @return int The targeted nodes count 259 | */ 260 | public function calculateApproxTargetNodes($startCount, $endCount) 261 | { 262 | if (1 === $startCount) { 263 | return $endCount; 264 | } 265 | if ($startCount <= $endCount) { 266 | $diff = $endCount - $startCount; 267 | if (1 < $diff) { 268 | if ($endCount / $startCount === 2) { 269 | 270 | return 2; 271 | } 272 | $target = (int) round($endCount/$startCount); 273 | 274 | return $target; 275 | } 276 | 277 | return 1; 278 | } 279 | 280 | $diff = (int) round($startCount/$endCount); 281 | $newStart = $startCount/$diff; 282 | 283 | return $this->calculateApproxTargetNodes($newStart, $endCount); 284 | } 285 | 286 | /** 287 | * @param RelationshipDefinition $relationship 288 | * @param ObjectCollection $targetNodes 289 | * @return int 290 | */ 291 | public function getTargetNodesCount(RelationshipDefinition $relationship, ObjectCollection $targetNodes) 292 | { 293 | $targetCount = $targetNodes->count(); 294 | if ($relationship->hasPercentage()) { 295 | $pct = $relationship->getPercentage(); 296 | } else { 297 | $pct = $targetCount <= 100 ? 60 : 20; 298 | } 299 | 300 | $percentage = $pct/100; 301 | $count = round($targetCount*$percentage); 302 | 303 | return (int) $count; 304 | } 305 | 306 | /** 307 | * @param $type 308 | * @param $sourceId 309 | * @param $sourceLabel 310 | * @param $targetId 311 | * @param $targetLabel 312 | * @param $properties 313 | * @param $seed 314 | * @return Relationship 315 | */ 316 | public function createRelationship($type, $sourceId, $sourceLabel, $targetId, $targetLabel, $properties, $seed) 317 | { 318 | $relationship = new Relationship(); 319 | $relationship->setType($type); 320 | $relationship->setSourceId($sourceId); 321 | $relationship->setSourceLabel($sourceLabel); 322 | $relationship->setTargetId($targetId); 323 | $relationship->setTargetLabel($targetLabel); 324 | foreach ($properties as $property) { 325 | $relationship->addProperty($property->getName(), $this->getFakeData($property, $seed)); 326 | } 327 | 328 | $this->relationships->add($relationship); 329 | } 330 | 331 | /** 332 | * @param ObjectCollection $nodes 333 | * @return mixed|null 334 | */ 335 | private function getRandomNode(ObjectCollection $nodes) 336 | { 337 | $max = $nodes->count(); 338 | $i = rand(0, $max-1); 339 | 340 | return $nodes->get($i); 341 | } 342 | 343 | /** 344 | * @param $usedNodes 345 | * @param ObjectCollection $collection 346 | * @param null $avoidSelf 347 | * @param bool $shuffle 348 | * @return int|null|string 349 | */ 350 | private function getNotUsedNodePosition($usedNodes, ObjectCollection $collection, $avoidSelf = null, $shuffle = false) 351 | { 352 | foreach ($collection as $k => $n) { 353 | if (!array_key_exists($k, $usedNodes)) { 354 | if (null !== $avoidSelf) { 355 | if ($n !== $avoidSelf) { 356 | return $k; 357 | } 358 | } else { 359 | return $k; 360 | } 361 | } 362 | } 363 | 364 | return null; 365 | } 366 | 367 | /** 368 | * @param Property $property 369 | * @param $seed 370 | * @return string|\DateTime|int|float|array|boolean 371 | */ 372 | private function getFakeData(Property $property, $seed) 373 | { 374 | $v = $this->faker->generate($property->getProvider(), $property->getArguments(), $seed, $property->isUnique()); 375 | 376 | return $this->sanitizeValueForGraph($v); 377 | } 378 | 379 | /** 380 | * Sanitizes values for Neo4j primitives. E.g.: DateTime objects are converted to strings 381 | * 382 | * @param $v 383 | * @return string 384 | */ 385 | private function sanitizeValueForGraph($v) 386 | { 387 | if ($v instanceof \DateTime) { 388 | return $v->format('Y-m-d H:i:s'); 389 | } 390 | 391 | return $v; 392 | } 393 | 394 | } 395 | -------------------------------------------------------------------------------- /src/Resources/config/services.yml: -------------------------------------------------------------------------------- 1 | services: 2 | neogen.parser_manager: 3 | class: "Neoxygen\\Neogen\\Parser\\ParserManager" 4 | 5 | neogen.graph_generator: 6 | class: "Neoxygen\\Neogen\\GraphGenerator\\Generator" 7 | arguments: ["@neogen.graph_processor"] 8 | 9 | neogen.faker: 10 | class: "%faker.class%" 11 | 12 | neogen.schema_builder: 13 | class: "Neoxygen\\Neogen\\Schema\\GraphSchemaBuilder" 14 | 15 | neogen.graph_processor: 16 | class: "Neoxygen\\Neogen\\Processor\\GraphProcessor" 17 | arguments: ["@neogen.faker"] 18 | 19 | neogen.graph_serializer: 20 | class: "Neoxygen\\Neogen\\Util\\GraphSerializer" 21 | -------------------------------------------------------------------------------- /src/Resources/models/blog.yml: -------------------------------------------------------------------------------- 1 | BlogPost: 2 | properties: 3 | title: sentence 4 | body: text 5 | created_at: unixTime 6 | -------------------------------------------------------------------------------- /src/Resources/models/business.yml: -------------------------------------------------------------------------------- 1 | Company: 2 | properties: 3 | name: company 4 | activity: catchPhrase 5 | -------------------------------------------------------------------------------- /src/Resources/models/people.yml: -------------------------------------------------------------------------------- 1 | Person: 2 | properties: 3 | firstname: firstName 4 | lastname: lastName 5 | birth_date: {type: dateTimeBetween, params: ["-50 years", "-18 years"]} 6 | -------------------------------------------------------------------------------- /src/Resources/models/social.yml: -------------------------------------------------------------------------------- 1 | Hashtag: 2 | properties: 3 | tag: hashtag 4 | 5 | Tweet: 6 | properties: 7 | text: {type:sentence, params:[10]} 8 | -------------------------------------------------------------------------------- /src/Resources/models/system.yml: -------------------------------------------------------------------------------- 1 | File: 2 | properties: 3 | name: word 4 | extension: fileExtension 5 | -------------------------------------------------------------------------------- /src/Resources/models/user.yml: -------------------------------------------------------------------------------- 1 | User: 2 | properties: 3 | _id: uuid 4 | login: username 5 | password: sha1 6 | email: email 7 | -------------------------------------------------------------------------------- /src/Schema/GraphSchema.php: -------------------------------------------------------------------------------- 1 | nodes = new ObjectCollection(); 28 | $this->relationships = new ObjectCollection(); 29 | } 30 | 31 | /** 32 | * @return ObjectCollection[\Neoxygen\Neogen\Schema\Node] 33 | */ 34 | public function getNodes() 35 | { 36 | return $this->nodes; 37 | } 38 | 39 | /** 40 | * @return ObjectCollection[\Neoxygen\Neogen\Schema\Relationship] 41 | */ 42 | public function getRelationships() 43 | { 44 | return $this->relationships; 45 | } 46 | 47 | /** 48 | * Adds a node to the nodes collection 49 | * 50 | * @param Node $node 51 | * @return bool 52 | * @throws SchemaDefinitionException 53 | */ 54 | public function addNode(Node $node) 55 | { 56 | foreach ($this->nodes as $n) { 57 | if ($n->getIdentifier() === $node->getIdentifier()) { 58 | throw new SchemaDefinitionException(sprintf('The node with Identifier "%s" has already been declared', $node->getIdentifier())); 59 | } 60 | } 61 | 62 | return $this->nodes->add($node); 63 | } 64 | 65 | /** 66 | * Adds a relationship to the relationship collection 67 | * 68 | * @param Relationship $relationship 69 | * @return bool 70 | * @throws SchemaDefinitionException 71 | */ 72 | public function addRelationship(Relationship $relationship) 73 | { 74 | foreach ($this->relationships as $rel) { 75 | if ($rel->getType() === $relationship->getType() && 76 | $rel->getStartNode() === $relationship->getStartNode() && 77 | $rel->getEndNode() === $relationship->getEndNode()) { 78 | throw new SchemaDefinitionException(sprintf('There is already a relationship declared with TYPE "%s" and STARTNODE "%s" and ENDNODE "%s"', 79 | $relationship->getType(), $relationship->getStartNode(), $relationship->getEndNode())); 80 | } 81 | } 82 | 83 | return $this->relationships->add($relationship); 84 | } 85 | 86 | public function toArray() 87 | { 88 | return array( 89 | 'nodes' => $this->nodes->getValues(), 90 | 'relationships' => $this->relationships->getValues() 91 | ); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Schema/GraphSchemaBuilder.php: -------------------------------------------------------------------------------- 1 | $nodeInfo) { 25 | $node = $this->buildNode($id, $nodeInfo); 26 | $graphSchema->addNode($node); 27 | } 28 | if (!isset($userSchema['relationships'])) { 29 | $userSchema['relationships'] = []; 30 | } 31 | foreach ($userSchema['relationships'] as $rel) { 32 | $relationship = $this->buildRelationship($rel); 33 | $graphSchema->addRelationship($relationship); 34 | } 35 | 36 | return $graphSchema; 37 | } 38 | 39 | /** 40 | * @param string $identifier 41 | * @param array $nodeInfo 42 | * @return Node 43 | */ 44 | public function buildNode($identifier, array $nodeInfo) 45 | { 46 | $node = new Node($identifier); 47 | $node->setAmount($nodeInfo['count']); 48 | if (is_string($nodeInfo['labels'])) { 49 | $node->addLabel($nodeInfo['labels']); 50 | } elseif (is_array($nodeInfo['labels'])) { 51 | $node->addLabels($nodeInfo['labels']); 52 | } 53 | if (!is_array($nodeInfo['properties'])){ 54 | $nodeInfo['properties'] = []; 55 | } 56 | foreach ($nodeInfo['properties'] as $key => $info) { 57 | $property = $this->buildNodeProperty($key, $info); 58 | $node->addProperty($property); 59 | } 60 | 61 | return $node; 62 | } 63 | 64 | /** 65 | * @param string $name 66 | * @param string|array $info 67 | * @return NodeProperty 68 | */ 69 | public function buildNodeProperty($name, $info) 70 | { 71 | $property = new NodeProperty($name); 72 | if (is_array($info)) { 73 | $property->setProvider($info['type'], $info['params']); 74 | } else { 75 | $property->setProvider($info); 76 | } 77 | 78 | return $property; 79 | } 80 | 81 | /** 82 | * Builds the relationship object based on user schema 83 | * 84 | * @param array $relInfo relationship info from user schema 85 | * @return Relationship 86 | */ 87 | public function buildRelationship(array $relInfo) 88 | { 89 | $relationship = new Relationship($relInfo['start'], $relInfo['end'], $relInfo['type']); 90 | $relationship->setCardinality($relInfo['mode']); 91 | if (isset($relInfo['properties'])) { 92 | foreach ($relInfo['properties'] as $name => $info) { 93 | $property = $this->buildRelationshipProperty($name, $info); 94 | $relationship->addProperty($property); 95 | } 96 | } 97 | 98 | return $relationship; 99 | } 100 | 101 | /** 102 | * @param string $name 103 | * @param string|array $info 104 | * @return RelationshipProperty 105 | */ 106 | public function buildRelationshipProperty($name, $info) 107 | { 108 | if (0 === strpos($name, '!')) { 109 | $name = substr($name, 1, strlen($name)-1); 110 | $unique = true; 111 | } else { 112 | $unique = false; 113 | } 114 | $property = new RelationshipProperty($name); 115 | if (is_array($info)) { 116 | $property->setProvider($info['type'], $info['params']); 117 | } else { 118 | $property->setProvider($info); 119 | } 120 | 121 | if ($unique) { 122 | $property->setUnique(); 123 | } 124 | 125 | return $property; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/Schema/Node.php: -------------------------------------------------------------------------------- 1 | identifier = (string) $identifier; 36 | $this->properties = new ObjectCollection(); 37 | $this->labels = new ObjectCollection(); 38 | $this->amount = 1; 39 | } 40 | 41 | /** 42 | * 43 | * Get Node Properties 44 | * 45 | * @return ObjectCollection[\Neoxygen\Neogen\Schema\NodeProperty] A collection of properties for this node 46 | */ 47 | public function getProperties() 48 | { 49 | return $this->properties; 50 | } 51 | 52 | /** 53 | * Adds a property to the node 54 | * 55 | * @param NodeProperty $property 56 | * @return bool 57 | */ 58 | public function addProperty(NodeProperty $property) 59 | { 60 | foreach ($this->properties as $prop) { 61 | if ($prop->getName() === $property->getName()) { 62 | $this->properties->removeElement($prop); 63 | } 64 | } 65 | 66 | return $this->properties->add($property); 67 | } 68 | 69 | /** 70 | * Returns the properties count for this node 71 | * 72 | * @return int 73 | */ 74 | public function getPropertiesCount() 75 | { 76 | return $this->properties->count(); 77 | } 78 | 79 | /** 80 | * Returns whether or not this node has properties 81 | * 82 | * @return bool 83 | */ 84 | public function hasProperties() 85 | { 86 | if ($this->properties->isEmpty()) { 87 | return false; 88 | } 89 | 90 | return true; 91 | } 92 | 93 | /** 94 | * Get all the properties that are indexed 95 | * 96 | * @return array 97 | */ 98 | public function getIndexedProperties() 99 | { 100 | $props = []; 101 | foreach ($this->properties as $property) { 102 | if ($property->isIndexed()) { 103 | $props[] = $property; 104 | } 105 | } 106 | 107 | return $props; 108 | } 109 | 110 | /** 111 | * Get all the properties that are unique 112 | * 113 | * @return array 114 | */ 115 | public function getUniqueProperties() 116 | { 117 | $props = []; 118 | foreach ($this->properties as $property) { 119 | if ($property->isUnique()) { 120 | $props[] = $property; 121 | } 122 | } 123 | 124 | return $props; 125 | } 126 | 127 | /** 128 | * Get the node identifier 129 | * 130 | * @return string 131 | */ 132 | public function getIdentifier() 133 | { 134 | return $this->identifier; 135 | } 136 | 137 | /** 138 | * Returns the node labels 139 | * 140 | * @return ObjectCollection 141 | */ 142 | public function getLabels() 143 | { 144 | return $this->labels; 145 | } 146 | 147 | /** 148 | * Adds a label to this node, checks if the label does not exist to avoid duplicates 149 | * 150 | * @param string $label 151 | * @return bool 152 | */ 153 | public function addLabel($label) 154 | { 155 | if (null !== $label) { 156 | $l = (string) $label; 157 | if (!$this->hasLabel($l)) { 158 | $this->labels->add($l); 159 | 160 | return true; 161 | } 162 | } 163 | 164 | return false; 165 | } 166 | 167 | /** 168 | * Adds multiple labels to this node 169 | * 170 | * @param array $labels 171 | */ 172 | public function addLabels(array $labels) 173 | { 174 | foreach ($labels as $label) { 175 | $this->addLabel($label); 176 | } 177 | } 178 | 179 | /** 180 | * Checks whether or not this node has the specified label 181 | * 182 | * @param string $label 183 | * @return bool 184 | */ 185 | public function hasLabel($label) 186 | { 187 | if (null !== $label) { 188 | $l = (string) $label; 189 | if ($this->labels->contains($l)) { 190 | return true; 191 | } 192 | } 193 | 194 | return false; 195 | } 196 | 197 | /** 198 | * Checks whether or not the node has a property with the specified name 199 | * 200 | * @param $name 201 | * @return bool 202 | */ 203 | public function hasProperty($name) 204 | { 205 | if (null !== $name) { 206 | $n = (string) $name; 207 | foreach ($this->properties as $property) { 208 | if ($property->getName() === $n) { 209 | return true; 210 | } 211 | } 212 | } 213 | 214 | return false; 215 | } 216 | 217 | public function getAmount() 218 | { 219 | return $this->amount; 220 | } 221 | 222 | public function setAmount($amount) 223 | { 224 | if (null !== $amount) { 225 | $this->amount = (int) $amount; 226 | } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/Schema/NodeProperty.php: -------------------------------------------------------------------------------- 1 | indexed) { 14 | return true; 15 | } 16 | 17 | return false; 18 | } 19 | 20 | public function setIndexed() 21 | { 22 | $this->unique = false; 23 | $this->indexed = true; 24 | } 25 | 26 | public function isUnique() 27 | { 28 | if ($this->unique) { 29 | return true; 30 | } 31 | 32 | return false; 33 | } 34 | 35 | public function setUnique() 36 | { 37 | $this->indexed = false; 38 | $this->unique = true; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Schema/Parser.php: -------------------------------------------------------------------------------- 1 | (:Company)<-[:BOOKED_PROJECT]-(:Customer) 12 | * 13 | * Class PatternParser 14 | * @package Neoxygen\Neogen\Schema 15 | * 16 | * 17 | */ 18 | 19 | class PatternParser 20 | { 21 | const INGOING_RELATIONSHIP = 'IN'; 22 | 23 | const OUTGOING_RELATIONSHIP = 'OUT'; 24 | 25 | const DEFAULT_RELATIONSHIP_DIRECTION = 'OUT'; 26 | 27 | private $nodes = []; 28 | 29 | private $relationships = []; 30 | 31 | private $processor; 32 | 33 | public function __construct() 34 | { 35 | $this->processor = new Processor(); 36 | } 37 | 38 | public function process($text) 39 | { 40 | $lines = explode("\n", $text); 41 | foreach ($lines as $line) { 42 | $this->parse($line); 43 | } 44 | } 45 | 46 | public function parse($pattern = null) 47 | { 48 | if (null === $pattern) { 49 | $cypherPattern = '(:Person 25)-[:KNOWS n..n]->(:Person)-[:WROTE 1..n]->(:Post 100)<-[:COMMENTED n..n]-(:Person)'; 50 | print($cypherPattern); 51 | //$cypherPattern = '(:Kid)'; 52 | } else { 53 | $cypherPattern = $pattern; 54 | } 55 | 56 | $nodePattern = '/(\\([-:,*.{}\\w\\s]*\\))/'; 57 | $relPattern = '/((-?)(\\[[:*.\\s\\w]+\\])(-?))/'; 58 | 59 | $split = preg_split($nodePattern, $cypherPattern, null, PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE); 60 | print_r($split); 61 | foreach ($split as $key => $part) { 62 | if (0 === $key && !preg_match($nodePattern, $part)) { 63 | throw new \InvalidArgumentException(sprintf('The pattern must start with a node part, "%s" given', $part)); 64 | } 65 | 66 | if (preg_match($nodePattern, $part)) { 67 | $this->processNodePart($part, $key); 68 | } elseif (preg_match($relPattern, $part)) { 69 | $this->processRelationshipPart($key, $split); 70 | } else { 71 | throw new \InvalidArgumentException(sprintf('Unable to parse part "%s"', $part)); 72 | } 73 | } 74 | } 75 | 76 | public function processNodePart($nodePart, $key) 77 | { 78 | $labelPattern = '/(([\\w]]+):([\\w]+))+/'; 79 | $nodeCountPattern = '/(\\s+[\\d]+\\*)/'; 80 | 81 | // Labels matching 82 | preg_match($labelPattern, $nodePart, $output); 83 | print_r($output); 84 | exit(); 85 | $expl = explode(':', $output[0]); 86 | $labels = []; 87 | foreach ($expl as $label) { 88 | if (!empty($label)) { 89 | $labels[] = $label; 90 | } 91 | } 92 | 93 | if (isset($this->nodes[$labels[0]])) { 94 | return; 95 | } 96 | 97 | preg_match($nodeCountPattern, $nodePart, $countOutput); 98 | if (empty($countOutput)) { 99 | $count = 10; 100 | } else { 101 | $count = (int) str_replace('*', '', $countOutput[0]); 102 | } 103 | 104 | $node = [ 105 | 'label' => $labels[0], 106 | 'count' => $count 107 | ]; 108 | 109 | $this->nodes[$node['label']] = $node; 110 | 111 | } 112 | 113 | public function processRelationshipPart($schemaKey, $schema) 114 | { 115 | $outgoingPattern = '/^-/'; 116 | $ingoingPattern = '/^<-/'; 117 | $relTypePattern = '/(:[\\w]+)/'; 118 | $cardinalityPattern = '/(\\*([\\d]+|n)\\.\\.([\\d]+|n)\\*)/'; 119 | $relString = $schema[$schemaKey]; 120 | 121 | if (preg_match($outgoingPattern, $schema[$schemaKey])) { 122 | $direction = self::OUTGOING_RELATIONSHIP; 123 | } elseif (preg_match($ingoingPattern, $schema[$schemaKey])) { 124 | $direction = self::INGOING_RELATIONSHIP; 125 | } else { 126 | $direction = self::DEFAULT_RELATIONSHIP_DIRECTION; 127 | } 128 | 129 | $stripPattern = '/[():]+/'; 130 | 131 | switch ($direction) { 132 | case "OUT": 133 | $startl = $schema[$schemaKey-1]; 134 | $ex = explode ('*', $startl); 135 | $start = trim($ex[0]); 136 | $startNodeLabel = preg_replace($stripPattern, '', $start); 137 | $endl = $schema[$schemaKey+1]; 138 | $endx = explode('*', $endl); 139 | $end = trim($endx[0]); 140 | $endNodeLabel = preg_replace($stripPattern, '', $end); 141 | break; 142 | case "IN": 143 | $startl = $schema[$schemaKey+1]; 144 | $ex = explode ('*', $startl); 145 | $start = trim($ex[0]); 146 | $startNodeLabel = preg_replace($stripPattern, '', $start); 147 | $endl = $schema[$schemaKey-1]; 148 | $endx = explode('*', $endl); 149 | $end = trim($endx[0]); 150 | $endNodeLabel = preg_replace($stripPattern, '', $end); 151 | break; 152 | } 153 | 154 | // Guessing type of relationship 155 | preg_match($relTypePattern, $relString, $output); 156 | $type = str_replace(':', '', $output[0]); 157 | 158 | // Guessing cardinality 159 | preg_match($cardinalityPattern, $relString, $cardiOutput); 160 | $cardinality = $cardiOutput[0]; 161 | $mode = trim(str_replace('*', '', $cardinality)); 162 | 163 | $alias = 'n'.sha1(uniqid()); 164 | $this->relationships[$alias] = [ 165 | 'start' => $startNodeLabel, 166 | 'end' => $endNodeLabel, 167 | 'type' => $type, 168 | 'mode' => $mode 169 | ]; 170 | 171 | } 172 | 173 | public function getSchema() 174 | { 175 | $schema = [ 176 | 'nodes' => $this->nodes, 177 | 'relationships' => $this->relationships 178 | ]; 179 | 180 | return $schema; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/Schema/Property.php: -------------------------------------------------------------------------------- 1 | name = (string) $name; 29 | } 30 | 31 | /** 32 | * Gets the property key name 33 | * 34 | * @return string 35 | */ 36 | public function getName() 37 | { 38 | return $this->name; 39 | } 40 | 41 | /** 42 | * Gets the property faker provider to use 43 | * 44 | * @return string 45 | */ 46 | public function getProvider() 47 | { 48 | return $this->provider; 49 | } 50 | 51 | /** 52 | * Sets the property faker provider to use 53 | * 54 | * @param string $provider The property faker provider 55 | * @param array $arguments The property faker provider arguments (optional) 56 | */ 57 | public function setProvider($provider, array $arguments = array()) 58 | { 59 | if (null === $provider || '' === $provider) { 60 | throw new \InvalidArgumentException('A property faker provider name can not be empty'); 61 | } 62 | 63 | $this->provider = $provider; 64 | 65 | if (!empty($arguments)) { 66 | $this->arguments = $arguments; 67 | } 68 | } 69 | 70 | /** 71 | * Returns the property faker provider arguments 72 | * 73 | * @return array 74 | */ 75 | public function getArguments() 76 | { 77 | return $this->arguments; 78 | } 79 | 80 | /** 81 | * Checks whether or not the property faker provider has specified arguments 82 | * 83 | * @return bool 84 | */ 85 | public function hasArguments() 86 | { 87 | if (!empty($this->arguments)) { 88 | return true; 89 | } 90 | 91 | return false; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Schema/Relationship.php: -------------------------------------------------------------------------------- 1 | startNode = (string) $startNode; 49 | $this->endNode = (string) $endNode; 50 | $this->type = (string) $type; 51 | $this->properties = new ObjectCollection(); 52 | } 53 | 54 | /** 55 | * Returns the start node identifier 56 | * 57 | * @return string 58 | */ 59 | public function getStartNode() 60 | { 61 | return $this->startNode; 62 | } 63 | 64 | /** 65 | * Returns the end node identifier 66 | * 67 | * @return string 68 | */ 69 | public function getEndNode() 70 | { 71 | return $this->endNode; 72 | } 73 | 74 | /** 75 | * Returns the relationship type 76 | * 77 | * @return string 78 | */ 79 | public function getType() 80 | { 81 | return $this->type; 82 | } 83 | 84 | /** 85 | * Returns a collection of relationship properties objects 86 | * 87 | * @return ObjectCollection[\Neoxygen\Neogen\Schema\RelationshipProperty] 88 | */ 89 | public function getProperties() 90 | { 91 | return $this->properties; 92 | } 93 | 94 | /** 95 | * Adds a relationship property to the collection and avoid duplicated 96 | * 97 | * @param RelationshipProperty $property 98 | * @return bool 99 | */ 100 | public function addProperty(RelationshipProperty $property) 101 | { 102 | foreach ($this->properties as $prop) { 103 | if ($prop->getName() === $property->getName()) { 104 | $this->properties->removeElement($prop); 105 | } 106 | } 107 | 108 | return $this->properties->add($property); 109 | } 110 | 111 | /** 112 | * Returns whether or not this relationship has properties 113 | * 114 | * @return bool 115 | */ 116 | public function hasProperties() 117 | { 118 | if ($this->properties->isEmpty()) { 119 | return false; 120 | } 121 | 122 | return true; 123 | } 124 | 125 | /** 126 | * Checks whether or not this relationship has the property with the specified name 127 | * 128 | * @param string $name The relationship property name 129 | * @return bool 130 | */ 131 | public function hasProperty($name) 132 | { 133 | if (null !== $name) { 134 | $n = (string) $name; 135 | foreach ($this->properties as $property) { 136 | if ($property->getName() === $n) { 137 | return true; 138 | } 139 | } 140 | } 141 | 142 | return false; 143 | } 144 | 145 | public function getCardinality() 146 | { 147 | return $this->cardinality; 148 | } 149 | 150 | public function setCardinality($v) 151 | { 152 | if (null !== $v) { 153 | $this->cardinality = (string) $v; 154 | } 155 | } 156 | 157 | public function getPercentage() 158 | { 159 | return $this->percentage; 160 | } 161 | 162 | public function hasPercentage() 163 | { 164 | return null !== $this->percentage; 165 | } 166 | 167 | public function setPercentage($percentage) 168 | { 169 | $pct = (int) $percentage; 170 | if (0 === $pct) { 171 | throw new SchemaDefinitionException(sprintf('A percentage of O is not allowed for the "%s" relationship', $this->getType())); 172 | } 173 | 174 | $this->percentage = $pct; 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/Schema/RelationshipProperty.php: -------------------------------------------------------------------------------- 1 | unique) { 20 | return true; 21 | } 22 | 23 | return false; 24 | } 25 | 26 | /** 27 | * Sets the relationship property as unique 28 | */ 29 | public function setUnique() 30 | { 31 | $this->unique = true; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Util/GraphSerializer.php: -------------------------------------------------------------------------------- 1 | serializer = SerializerBuilder::create() 15 | ->build(); 16 | } 17 | 18 | public function serializeGraphToJson(Graph $graph) 19 | { 20 | return $this->serializer->serialize($graph->toArray(), 'json'); 21 | } 22 | } -------------------------------------------------------------------------------- /src/Util/ObjectCollection.php: -------------------------------------------------------------------------------- 1 | build(); 12 | 13 | $gsb = new GraphSchemaBuilder(); 14 | $file = file_get_contents(__DIR__.'/neogen.cypher'); 15 | $p = $neogen->getParserManager()->getParser('cypher'); 16 | $userSchema = $p->parse($file); 17 | 18 | $def = $gsb->buildGraph($userSchema); 19 | $gen = $neogen->getGraphGenerator(); 20 | $g = $gen->generateGraph($def); 21 | 22 | $ser = $neogen->getGraphSerializer(); 23 | $json = $ser->serializeGraphToJson($g); 24 | 25 | //print_r($json); 26 | -------------------------------------------------------------------------------- /tests/Neoxygen/Neogen/Tests/BuilderTest.php: -------------------------------------------------------------------------------- 1 | build(); 18 | 19 | $this->neogen = $neogen; 20 | } 21 | 22 | public function testParserManagerIsRegistered() 23 | { 24 | $this->assertNotEmpty($this->neogen->getParserManager()); 25 | } 26 | 27 | public function testYamlParserIsAvailable() 28 | { 29 | $this->assertInstanceOf('Neoxygen\Neogen\Parser\YamlFileParser', $this->neogen->getParser('yaml')); 30 | } 31 | 32 | public function testCypherPaserIsAvailable() 33 | { 34 | $this->assertInstanceOf('Neoxygen\Neogen\Parser\CypherPattern', $this->neogen->getParser('cypher')); 35 | } 36 | 37 | public function testGraphGeneratorIsAvailable() 38 | { 39 | $this->assertInstanceOf('Neoxygen\Neogen\GraphGenerator\Generator', $this->neogen->getGraphGenerator()); 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /tests/Neoxygen/Neogen/Tests/Functional/SimpleYamlGraphTest.php: -------------------------------------------------------------------------------- 1 | neogen = Neogen::create() 15 | ->build(); 16 | } 17 | 18 | public function testYamlSchemaIsParsed() 19 | { 20 | $neogen = $this->neogen; 21 | $path = __DIR__.'/simple_schema.yml'; 22 | $parser = $neogen->getParser('yaml'); 23 | $userSchema = $parser->parse($path); 24 | 25 | $this->assertArrayHasKey('nodes', $userSchema); 26 | $this->assertArrayHasKey('person', $userSchema['nodes']); 27 | $this->assertArrayHasKey('labels', $userSchema['nodes']['person']); 28 | $this->assertArrayHasKey('birth', $userSchema['nodes']['person']['properties']); 29 | } 30 | 31 | public function testGraphIsGenerated() 32 | { 33 | $neogen = $this->neogen; 34 | $path = __DIR__.'/simple_schema.yml'; 35 | $parser = $neogen->getParser('yaml'); 36 | $userSchema = $parser->parse($path); 37 | $graph = $neogen->generateGraph($userSchema); 38 | 39 | $this->assertInstanceOf('Neoxygen\Neogen\Graph\Graph', $graph); 40 | $this->assertEquals(3, $graph->getNodes()->count()); 41 | foreach ($graph->getNodes() as $node) { 42 | $this->assertEquals('Person', $node->getLabel()); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /tests/Neoxygen/Neogen/Tests/Functional/simple_schema.yml: -------------------------------------------------------------------------------- 1 | nodes: 2 | person: 3 | labels: Person 4 | count: 3 5 | properties: 6 | id: uuid 7 | birth: {type: dateTimeBetween, params:["-65 years","-18 years"]} -------------------------------------------------------------------------------- /tests/Unit/Parser/CypherPatternParserTest.php: -------------------------------------------------------------------------------- 1 | preFormatPattern($pattern); 22 | $this->assertEquals("(a:Node)\n(b:Node)", $cleaned); 23 | } 24 | 25 | public function testCommentedLinesAreStriped() 26 | { 27 | $parser = new CypherPattern(); 28 | $pattern = "//(a:Node)-[:RELATES_TO]->(b)\n(b:Node)"; 29 | $this->assertEquals("(b:Node)", $parser->preFormatPattern($pattern)); 30 | } 31 | 32 | public function testLineIsParsedAndSplitted() 33 | { 34 | $parser = new CypherPattern(); 35 | $pattern = "(a:Node)-[:RELATES_TO *n..n]->(b:Node)"; 36 | $extract = $parser->parseLine($pattern); 37 | $this->assertCount(3, $extract); 38 | } 39 | 40 | public function testMatchNodePattern() 41 | { 42 | $parser = new CypherPattern(); 43 | $str = '(a:Node:#SuperNode:Person {uuid: {randomNumber:[0,20]}} *100)'; 44 | $definition = $parser->matchPattern($str); 45 | 46 | $this->assertEquals("a", $definition->getIdentifier()); 47 | $this->assertCount(3, $definition->getLabels()); 48 | $this->assertCount(1, $definition->getModels()); 49 | } 50 | 51 | public function testIdentifierIsRequired() 52 | { 53 | $this->setExpectedException(ParseException::class); 54 | $parser = new CypherPattern(); 55 | $str = '(:Node *1)'; 56 | $parser->matchPattern($str); 57 | } 58 | 59 | public function testAtLeastOneLabelIsRequired() 60 | { 61 | $this->setExpectedException(ParseException::class); 62 | $parser = new CypherPattern(); 63 | $str = '(a *1)'; 64 | $parser->matchPattern($str); 65 | } 66 | 67 | public function testIndexMarkIsTaken() 68 | { 69 | $parser = new CypherPattern(); 70 | $str = '(a:Node {?uuid: uuid})'; 71 | $definition = $parser->matchPattern($str); 72 | 73 | $this->assertCount(1, $definition->getProperties()); 74 | foreach ($definition->getProperties() as $prop) { 75 | $this->assertTrue($prop->isIndexed()); 76 | } 77 | } 78 | 79 | public function testUniqueConstraintMarkIsTaken() 80 | { 81 | $parser = new CypherPattern(); 82 | $str = '(a:Node {!uuid: uuid})'; 83 | $definition = $parser->matchPattern($str); 84 | 85 | $this->assertCount(1, $definition->getProperties()); 86 | foreach ($definition->getProperties() as $prop) { 87 | $this->assertTrue($prop->isUnique()); 88 | } 89 | } 90 | 91 | public function testOtherMarkersThrowErrors() 92 | { 93 | $str = '(w:Node {;uuid: uuid})'; 94 | $parser = new CypherPattern(); 95 | $this->setExpectedException(ParseException::class); 96 | $parser->matchPattern($str); 97 | } 98 | 99 | public function testTacksAreNotAllowedInPropertyKeys() 100 | { 101 | $str = '(w:Node {uuid-v1: uuid})'; 102 | $parser = new CypherPattern(); 103 | $this->setExpectedException(ParseException::class); 104 | $parser->matchPattern($str); 105 | } 106 | 107 | public function testMarkersAreRemovedInPropertyKeys() 108 | { 109 | $str = '(w:Node {!uuid: uuid, ?name: word})'; 110 | $parser = new CypherPattern(); 111 | $definition = $parser->matchPattern($str); 112 | $this->assertEquals('uuid', $definition->getProperties()[0]->getKey()); 113 | $this->assertEquals('name', $definition->getProperties()[1]->getKey()); 114 | } 115 | } -------------------------------------------------------------------------------- /tests/queries.txt: -------------------------------------------------------------------------------- 1 | (genre:Genre*3)-[:HAS_CHANNEL]->(channel:Channel*4)-[:HAS_PROGRAM]->(program:Program*3)-[:HAS_GENDER]->(gender:Gender*3)-[genderRelationDevice:HAS_PROGRAM_DISCOVERY_BY_DEVICE*0..n]->(programDiscoveryByDevice:ProgramDiscoveryByDevice*10) -------------------------------------------------------------------------------- /tests_old/Neoxygen/Neogen/Tests/Integration/CypherStatementsConverterTest.php: -------------------------------------------------------------------------------- 1 | (company:Company {name:company} *10) 15 | (actor:Person:Actor {name:fullName, birthdate:{dateTimeBetween:["-50 years","-18 years"]}} *10)-[:WORKS_AT {since:{dateTimeBetween:["-5 years","-1 years"]}} *n..1]->(company)'; 16 | $graph = $gen->generateGraphFromCypher($p); 17 | 18 | $converter = new CypherStatementsConverter(); 19 | $converter->convert($graph); 20 | 21 | $client = ClientBuilder::create() 22 | ->addDefaultLocalConnection() 23 | ->build(); 24 | 25 | $client->sendCypherQuery('MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE r,n'); 26 | 27 | $statements = $converter->getStatements(); 28 | foreach ($statements['constraints'] as $st){ 29 | $client->sendCypherQuery($st['statement']); 30 | } 31 | foreach ($statements['nodes'] as $st){ 32 | $client->sendCypherQuery($st['statement'], $st['parameters']); 33 | } 34 | foreach ($statements['edges'] as $st){ 35 | if (!isset($st['parameters'])){ 36 | $parameters = array(); 37 | } else { 38 | $parameters = $st['parameters']; 39 | } 40 | $client->sendCypherQuery($st['statement'], $parameters); 41 | } 42 | 43 | } 44 | } -------------------------------------------------------------------------------- /tests_old/Neoxygen/Neogen/Tests/Integration/GraphJsonTest.php: -------------------------------------------------------------------------------- 1 | (p) 15 | (p)-[:HAS *n..n]->(s:Skill {name: progLanguage} *20) 16 | (c:Company *20)-[:LOOKS_FOR_COMPETENCE *n..n]->(s) 17 | (c)-[:LOCATED_IN *n..1]->(country:Country {name: country} *70) 18 | (p)-[:LIVES_IN *n..1]->(country)'; 19 | $graph = $gen->generateGraphFromCypher($p); 20 | $converter = new GraphJSONConverter(); 21 | $json = $converter->convert($graph); 22 | //print_r($json); 23 | } 24 | 25 | public function testSimpleNode() 26 | { 27 | $gen = new Neogen(); 28 | $p = '(p:Person:User *3)'; 29 | $graph = $gen->generateGraphFromCypher($p); 30 | } 31 | 32 | public function testLinkedList() 33 | { 34 | $gen = new Neogen(); 35 | $p = '(test:Test *5) 36 | (test)-[:NEXT *1..1]->(test)'; 37 | $graph = $gen->generateGraphFromCypher($p); 38 | } 39 | 40 | 41 | } -------------------------------------------------------------------------------- /tests_old/Neoxygen/Neogen/Tests/Integration/IntegrationTest.php: -------------------------------------------------------------------------------- 1 | loadGraphInDB($pattern); 19 | 20 | $query = 'MATCH (n) RETURN n'; 21 | $result = $this->sendQuery($query); 22 | 23 | $this->assertEquals(35, $result->getNodesCount()); 24 | $this->assertEquals('Person', $result->getSingleNode()->getLabel()); 25 | } 26 | 27 | public function testBasicPatternWithSimpleProps() 28 | { 29 | $pattern = '(p:Person {firstname: firstName, digit: randomDigit} *10)'; 30 | $this->loadGraphInDB($pattern); 31 | 32 | $q = 'MATCH (n) RETURN n'; 33 | $result = $this->sendQuery($q); 34 | 35 | $this->assertEquals(10, $result->getNodesCount()); 36 | $this->assertEquals('Person', $result->getSingleNode()->getLabel()); 37 | $this->assertInternalType('integer', $result->getSingleNode()->getProperty('digit')); 38 | $this->assertTrue($result->getSingleNode()->hasProperty('firstname')); 39 | } 40 | 41 | public function testBasicPatternWithCustomProps() 42 | { 43 | $pattern = '(p:Person {firstname: firstName, age: {numberBetween: [18, 50]}} *10)'; 44 | $this->loadGraphInDB($pattern); 45 | 46 | $q = 'MATCH (n) RETURN n'; 47 | $result = $this->sendQuery($q); 48 | 49 | $this->assertEquals(10, $result->getNodesCount()); 50 | $this->assertEquals('Person', $result->getSingleNode()->getLabel()); 51 | $this->assertInternalType('integer', $result->getSingleNode()->getProperty('age')); 52 | $this->assertTrue($result->getSingleNode()->getProperty('age') >= 18 && $result->getSingleNode()->getProperty('age') <= 50); 53 | $this->assertTrue($result->getSingleNode()->hasProperty('firstname')); 54 | } 55 | 56 | public function testPatternWithEdges() 57 | { 58 | $pattern = '(p:Person *15)-[:WORKS_AT *n..1]->(c:Company {name: company} *7)'; 59 | $this->loadGraphInDB($pattern); 60 | 61 | $q = 'MATCH p=(n:Person)-[:WORKS_AT]->(c:Company) RETURN p'; 62 | $result = $this->sendQuery($q); 63 | 64 | $this->assertCount(15, $result->getNodesByLabel('Person')); 65 | $this->assertCount(1, $result->getSingleNode('Person')->getRelationships('WORKS_AT')); 66 | 67 | $q = 'MATCH (n:Company) RETURN n'; 68 | $result = $this->sendQuery($q); 69 | $this->assertCount(7, $result->getNodesByLabel('Company')); 70 | } 71 | 72 | public function testPatternWithEdgesAndProps() 73 | { 74 | $pattern = '(p:Person *15)-[:WORKS_AT {level: randomDigit} *n..1]->(c:Company {name: company} *7)'; 75 | $this->loadGraphInDB($pattern); 76 | 77 | $q = 'MATCH p=(n:Person)-[:WORKS_AT]->(c:Company) RETURN p'; 78 | $result = $this->sendQuery($q); 79 | 80 | $this->assertCount(15, $result->getNodesByLabel('Person')); 81 | $this->assertCount(1, $result->getSingleNode('Person')->getRelationships('WORKS_AT')); 82 | $this->assertTrue($result->getSingleNode()->getSingleRelationship('WORKS_AT')->hasProperty('level')); 83 | 84 | $q = 'MATCH (n:Company) RETURN n'; 85 | $result = $this->sendQuery($q); 86 | $this->assertCount(7, $result->getNodesByLabel('Company')); 87 | } 88 | 89 | public function testMultiplePatternsOnOneLine() 90 | { 91 | $pattern = '(p:Person *15)-[:WORKS_AT {level: randomDigit} *n..1]->(c:Company {name: company} *7)-[:IN_MARKET *n..1]->(m:Market *5)'; 92 | $this->loadGraphInDB($pattern); 93 | 94 | $q = 'MATCH p=(person:Person)-[*]->(m:Market) RETURN p'; 95 | $result = $this->sendQuery($q); 96 | 97 | $person = $result->getSingleNode('Person'); 98 | $this->assertTrue('Person' === $person->getLabel()); 99 | $this->assertEquals('WORKS_AT', $person->getSingleRelationship()->getType()); 100 | $this->assertEquals('Company', $person->getSingleRelationship()->getEndNode()->getLabel()); 101 | $this->assertEquals('Market', $person->getSingleRelationship()->getEndNode() 102 | ->getSingleRelationship('IN_MARKET')->getEndNode()->getLabel()); 103 | } 104 | 105 | public function testSiteExamplePattern() 106 | { 107 | $pattern = '(p:Person {firstname: firstName, lastname: lastName} *10)-[:KNOWS *n..n]->(p) 108 | (p)-[:HAS *n..n]->(s:Skill {name: progLanguage} *10) 109 | (c:Company {name: company, desc: catchPhrase} *5)-[:LOOKS_FOR_COMPETENCE *n..n]->(s) 110 | (c)-[:LOCATED_IN *n..1]->(country:Country {name: country} *5) 111 | (p)-[:LIVES_IN *n..1]->(country)'; 112 | 113 | $this->loadGraphInDB($pattern); 114 | $q = 'MATCH p=(n)-[*]-() RETURN p LIMIT 10'; 115 | $result = $this->sendQuery($q); 116 | 117 | $q = 'MATCH (n:Skill) RETURN n'; 118 | $result = $this->sendQuery($q); 119 | 120 | #$this->assertCount(10, $result->getNodes()); 121 | 122 | $q = 'MATCH p=(person:Person)-[:HAS]->() RETURN p'; 123 | $result = $this->sendQuery($q); 124 | 125 | $this->assertTrue($result->getSingleNode()->hasRelationships()); 126 | ; } 127 | 128 | public function test1NCardinality() 129 | { 130 | $p = '(g:Genre *6)-[:HAS_CHANNEL *1..n]->(channel:Channel *8)'; 131 | $this->loadGraphInDB($p); 132 | $query = 'MATCH p=(g:Genre)-[:HAS_CHANNEL]->(channel:Channel) RETURN p'; 133 | $result = $this->sendQuery($query); 134 | 135 | $this->assertCount(6, $result->getNodesByLabel('Genre')); 136 | $this->assertCount(8, $result->getNodesByLabel('Channel')); 137 | $this->assertCount(8, $result->getRelationships()); 138 | $this->assertEquals('HAS_CHANNEL', $result->getSingleNode()->getSingleRelationship()->getType()); 139 | 140 | $this->clearDB(); 141 | $p = '(g:Genre *6)-[:HAS_CHANNEL *1..n]->(channel:Channel *37)'; 142 | $this->loadGraphInDB($p); 143 | $query = 'MATCH p=(g:Genre)-[:HAS_CHANNEL]->(channel:Channel) RETURN p'; 144 | $result = $this->sendQuery($query); 145 | 146 | #$this->assertCount(6, $result->getNodesByLabel('Genre')); 147 | #$this->assertCount(37, $result->getNodesByLabel('Channel')); 148 | #$this->assertCount(37, $result->getRelationships()); 149 | $this->assertEquals('HAS_CHANNEL', $result->getSingleNode()->getSingleRelationship()->getType()); 150 | 151 | $this->clearDB(); 152 | $p = '(g:Genre *35)-[:HAS_CHANNEL *1..n]->(channel:Channel *8)'; 153 | $this->loadGraphInDB($p); 154 | $query = 'MATCH p=(g:Genre)-[:HAS_CHANNEL]->(channel:Channel) RETURN p'; 155 | $result = $this->sendQuery($query); 156 | #$this->assertCount(8, $result->getNodesByLabel('Channel')); 157 | #$this->assertCount(8, $result->getRelationships()); 158 | $this->clearDB(); 159 | } 160 | 161 | public function testLinkedList() 162 | { 163 | $p = '(root:Root *1)-[:LINK *1..n]->(link1:Link *1)-[:LINK *1..n]->(link2:Link2 *1)-[:LINK *1..n]->(root)'; 164 | $this->clearDB(); 165 | $this->loadGraphInDB($p); 166 | 167 | $q = 'MATCH p=(n)-[r]-() RETURN p'; 168 | $result = $this->sendQuery($q); 169 | 170 | $root = $result->getSingleNodeByLabel('Root'); 171 | $this->assertTrue($root->getSingleRelationship('LINK', 'OUT')->getEndNode()->getLabel() == 'Link'); 172 | //$link = $result->getSingleNodeByLabel('Link'); 173 | //$this->assertTrue($link->getSingleRelationship('LINK', 'OUT')->getEndNode()->getLabel()->getSingleRelationship('LINK', 'OUT')->getEndNode()->getLabel() == 'Root'); 174 | } 175 | 176 | public function testEdgeWithMultiProps() 177 | { 178 | $p = '(person:Person {name: firstName, lastname:{numberBetween:[2,5]}} *15)-[:WORKS_AT {weight:{numberBetween:[2,8]}, activity:{numberBetween:[2,50]}} *n..1]->(company:Company {name:company}*10)'; 179 | $this->clearDB(); 180 | $this->loadGraphInDB($p); 181 | 182 | $q = 'MATCH p=(:Person)-[:WORKS_AT]->(:Company) RETURN p'; 183 | $result = $this->sendQuery($q); 184 | 185 | $this->assertCount(15, $result->getRelationships()); 186 | 187 | } 188 | 189 | public function testOneToOneCardinality() 190 | { 191 | $p = '(root:Root)-[:CHILD *1..1]->(child:Child)'; 192 | $this->clearDB(); 193 | $this->loadGraphInDB($p); 194 | 195 | $q = 'MATCH p=(root:Root)-[r]-() RETURN p'; 196 | $result = $this->sendQuery($q); 197 | 198 | $this->assertTrue($result->getSingleNode('Root')->getLabel() == 'Root'); 199 | $this->assertTrue($result->getSingleNode('Root')->getSingleRelationship()->getType() == 'CHILD'); 200 | $this->assertTrue($result->getSingleNode('Root')->getSingleRelationship()->getEndNode()->getLabel() == 'Child'); 201 | 202 | } 203 | 204 | public function testOneToOneCardinality2() 205 | { 206 | $p = '(root:Root2 *10)-[:CHILD2 *1..1]->(child:Child *7)'; 207 | $this->clearDB(); 208 | $this->loadGraphInDB($p); 209 | 210 | $q = 'MATCH p=(root:Root2)-[r:CHILD2]-() RETURN p'; 211 | $result = $this->sendQuery($q); 212 | 213 | $this->assertTrue($result->getSingleNode('Root2')->getLabel() == 'Root2'); 214 | $this->assertTrue($result->getSingleNode('Root2')->getSingleRelationship()->getType() == 'CHILD2'); 215 | $this->assertTrue($result->getSingleNode('Root2')->getSingleRelationship()->getEndNode()->getLabel() == 'Child'); 216 | $this->assertEquals(7, $result->getRelationshipsCount()); 217 | 218 | } 219 | 220 | public function testOneToOneCardinality3() 221 | { 222 | $p = '(root:Root3 *5)-[:CHILD3 *1..1]->(child:Child *7)'; 223 | $this->clearDB(); 224 | $this->loadGraphInDB($p); 225 | 226 | $q = 'MATCH p=(root:Root3)-[r:CHILD3]-() RETURN p'; 227 | $result = $this->sendQuery($q); 228 | 229 | $this->assertTrue($result->getSingleNode('Root3')->getLabel() == 'Root3'); 230 | $this->assertTrue($result->getSingleNode('Root3')->getSingleRelationship()->getType() == 'CHILD3'); 231 | $this->assertTrue($result->getSingleNode('Root3')->getSingleRelationship()->getEndNode()->getLabel() == 'Child'); 232 | $this->assertEquals(5, $result->getRelationshipsCount()); 233 | 234 | } 235 | 236 | public function getClient() 237 | { 238 | if (null === $this->client) { 239 | $client = ClientBuilder::create() 240 | ->addDefaultLocalConnection() 241 | ->setAutoFormatResponse(true) 242 | ->build(); 243 | 244 | $this->client = $client; 245 | } 246 | 247 | return $this->client; 248 | } 249 | 250 | public function sendQuery($q, array $p = array()) 251 | { 252 | $response = $this->getClient()->sendCypherQuery($q, $p); 253 | sleep(0.2); 254 | $result = $response->getResult(); 255 | 256 | return $result; 257 | } 258 | 259 | public function loadGraphInDB($pattern) 260 | { 261 | $this->clearDB(); 262 | $gen = new Neogen(); 263 | $schema = $gen->generateGraphFromCypher($pattern); 264 | 265 | $converter = new CypherStatementsConverter(); 266 | $converter->convert($schema); 267 | 268 | $statements = $converter->getStatements(); 269 | foreach ($statements as $statement){ 270 | if (is_array($statement)){ 271 | foreach($statement as $st){ 272 | $props = isset($st['parameters']) ? $st['parameters'] : array(); 273 | $this->sendQuery($st['statement'], $props); 274 | } 275 | } else { 276 | $props = isset($statement['parameters']) ? $statement['parameters'] : array(); 277 | $this->sendQuery($statement['statement'], $props); 278 | } 279 | } 280 | } 281 | 282 | public function clearDB() 283 | { 284 | $q = 'MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE r,n'; 285 | $this->sendQuery($q); 286 | } 287 | } -------------------------------------------------------------------------------- /tests_old/Neoxygen/Neogen/Tests/Integration/StandardCypherConverterTest.php: -------------------------------------------------------------------------------- 1 | (company:Company {name:company} *10) 14 | (actor:Person:Actor {name:fullName, birthdate:{dateTimeBetween:["-50 years","-18 years"]}} *10)-[:WORKS_AT {since:{dateTimeBetween:["-5 years","-1 years"]}} *n..1]->(company)'; 15 | $graph = $gen->generateGraphFromCypher($p); 16 | $converter = new StandardCypherConverter(); 17 | $converter->convert($graph); 18 | 19 | $file = getcwd().'/sts.cql'; 20 | $contents = ''; 21 | foreach($converter->getStatements() as $st){ 22 | $contents .= $st . "\n"; 23 | } 24 | file_put_contents($file, $contents); 25 | 26 | } 27 | } -------------------------------------------------------------------------------- /tests_old/Neoxygen/Neogen/Tests/Parser/NewCypherPatternTest.php: -------------------------------------------------------------------------------- 1 | parseCypher($p); 16 | $schema = $parser->getSchema(); 17 | $this->assertArrayHasKey('person', $schema->getNodes()); 18 | $this->assertCount(1, $schema->getNodes()['person']['labels']); 19 | $this->assertEquals(10, $schema->getNodes()['person']['count']); 20 | } 21 | 22 | public function testTwoSimpleNodes() 23 | { 24 | $parser = new CypherPattern(); 25 | $p = '(person:Person) 26 | (chart:Chart)'; 27 | $parser->parseCypher($p); 28 | $schema = $parser->getSchema(); 29 | $this->assertArrayHasKey('person', $schema->getNodes()); 30 | $this->assertArrayHasKey('chart', $schema->getNodes()); 31 | $this->assertCount(1, $schema->getNodes()['person']['labels']); 32 | $this->assertCount(1, $schema->getNodes()['chart']['labels']); 33 | } 34 | 35 | public function testSimpleNodeWithProperties() 36 | { 37 | $p = '(person:Person {id:uuid})'; 38 | $parser = new CypherPattern(); 39 | $parser->parseCypher($p); 40 | $schema = $parser->getSchema(); 41 | $this->assertArrayHasKey('person', $schema->getNodes()); 42 | $this->assertCount(1, $schema->getNodes()['person']['labels']); 43 | $this->assertCount(1, $schema->getNodes()['person']['properties']); 44 | } 45 | 46 | public function testSimpleEdge() 47 | { 48 | $p = '(person:Person {id:uuid})-[:WORKS_AT *n..1]->(company:Company)'; 49 | $parser = new CypherPattern(); 50 | $parser->parseCypher($p); 51 | $schema = $parser->getSchema(); 52 | $this->assertArrayHasKey('person', $schema->getNodes()); 53 | $this->assertCount(1, $schema->getNodes()['person']['labels']); 54 | $this->assertCount(1, $schema->getNodes()['person']['properties']); 55 | $this->assertArrayHasKey('company', $schema->getNodes()); 56 | $this->assertCount(1, $schema->getEdges()); 57 | $this->assertEquals('WORKS_AT', $schema->getEdges()[0]['type']); 58 | $this->assertEquals('person', $schema->getEdges()[0]['start']); 59 | $this->assertEquals('company', $schema->getEdges()[0]['end']); 60 | } 61 | 62 | public function testSimpleEdgeInverse() 63 | { 64 | $p = '(person:Person {id:uuid})<-[:WORKS_AT *n..1]-(company:Company)'; 65 | $parser = new CypherPattern(); 66 | $parser->parseCypher($p); 67 | $schema = $parser->getSchema(); 68 | $this->assertArrayHasKey('person', $schema->getNodes()); 69 | $this->assertCount(1, $schema->getNodes()['person']['labels']); 70 | $this->assertCount(1, $schema->getNodes()['person']['properties']); 71 | $this->assertArrayHasKey('company', $schema->getNodes()); 72 | $this->assertCount(1, $schema->getEdges()); 73 | $this->assertEquals('WORKS_AT', $schema->getEdges()[0]['type']); 74 | $this->assertEquals('person', $schema->getEdges()[0]['end']); 75 | $this->assertEquals('company', $schema->getEdges()[0]['start']); 76 | } 77 | 78 | public function testReuseOfIdentifier() 79 | { 80 | $p = '(person:Person {id:uuid} *10)-[:WORKS_AT *n..1]->(company:Company *5) 81 | (person)-[:KNOWS *n..n]->(person)'; 82 | $parser = new CypherPattern(); 83 | $parser->parseCypher($p); 84 | $schema = $parser->getSchema(); 85 | $this->assertArrayHasKey('person', $schema->getNodes()); 86 | $this->assertCount(1, $schema->getNodes()['person']['labels']); 87 | $this->assertCount(1, $schema->getNodes()['person']['properties']); 88 | $this->assertArrayHasKey('company', $schema->getNodes()); 89 | $this->assertCount(2, $schema->getEdges()); 90 | $this->assertCount(2, $schema->getNodes()); 91 | } 92 | 93 | public function testModelLayerIdentifier() 94 | { 95 | $p = '(person:#Person {id:uuid} *10)-[:WORKS_AT *n..1]->(company:#Company *5) 96 | (person)-[:KNOWS *n..n]->(person)'; 97 | $label = '#Person'; 98 | $parser = new CypherPattern(); 99 | $parser->parseCypher($p); 100 | $schema = $parser->getSchema(); 101 | $this->assertCount(1, $schema->getNodes()['person']['labels']); 102 | $this->assertCount(1, $schema->getNodes()['person']['properties']); 103 | $this->assertArrayHasKey('company', $schema->getNodes()); 104 | $this->assertCount(2, $schema->getEdges()); 105 | $this->assertCount(2, $schema->getNodes()); 106 | $this->assertEquals('Company', $schema->getNodes()['company']['models'][0]); 107 | } 108 | 109 | public function testErrorWhenNoIdentifier() 110 | { 111 | $p = '(:Person)'; 112 | $this->setExpectedException($this->schemaException); 113 | $parser = new CypherPattern(); 114 | $parser->parseCypher($p); 115 | } 116 | 117 | public function testErrorWhenNoCardinality() 118 | { 119 | $parser = new CypherPattern(); 120 | $p = '(p:Person)-[:WORKS_AT]->(p)'; 121 | $this->setExpectedException($this->schemaException); 122 | $parser->parseCypher($p); 123 | } 124 | 125 | public function testErrorWhenNoEdgeType() 126 | { 127 | $parser = new CypherPattern(); 128 | $p = '(p:Person)-[ *n..1]->(p)'; 129 | $this->setExpectedException($this->schemaException); 130 | $parser->parseCypher($p); 131 | } 132 | 133 | public function testErrorWhenBadPattern() 134 | { 135 | $parser = new CypherPattern(); 136 | $p = ':Person *10)'; 137 | $this->setExpectedException($this->schemaException); 138 | $parser->parseCypher($p); 139 | } 140 | 141 | public function testErrorWhenIdentifierWasNotDeclared() 142 | { 143 | $p = '(post)-[:WRITTEN_BY *n..1]->(user:User)'; 144 | $parser = new CypherPattern(); 145 | $this->setExpectedException($this->schemaException); 146 | $parser->parseCypher($p); 147 | } 148 | } -------------------------------------------------------------------------------- /tests_old/Neoxygen/Neogen/Tests/Processor/VertEdgeProcessorTest.php: -------------------------------------------------------------------------------- 1 | getSchema($p); 14 | $processor = new VertEdgeProcessor(); 15 | $processor->process($schema); 16 | $graph = $processor->getGraph(); 17 | $this->assertCount(1, $graph['nodes']); 18 | } 19 | 20 | public function testSimplePattern() 21 | { 22 | $p = '(person:Person *10)-[:KNOWS *n..n]->(person)'; 23 | $schema = $this->getSchema($p); 24 | $processor = new VertEdgeProcessor(); 25 | $processor->process($schema); 26 | $graph = $processor->getGraph(); 27 | $this->assertCount(10, $graph['nodes']); 28 | $this->assertTrue(count($graph['edges']) > 10); 29 | } 30 | 31 | private function getSchema($pattern) 32 | { 33 | $parser = new CypherPattern(); 34 | $parser->parseCypher($pattern); 35 | 36 | return $parser->getSchema(); 37 | } 38 | } -------------------------------------------------------------------------------- /tests_old/Neoxygen/Neogen/Tests/ProcessorTest.php: -------------------------------------------------------------------------------- 1 | (p) 14 | (csc:ComponentSubCategory *20)-[:PART_OF {quantity: {randomNumber: [3]}} *n..1]->(cc) 15 | (c:Component {price: {randomNumber: [2]}} *20)-[:PART_OF {quantity: {randomNumber: [3]}} *n..1]->(csc)'; 16 | 17 | $gen = new Neogen(); 18 | $schema = $gen->generateGraphFromCypher($p); 19 | $converter = new CypherStatementsConverter(); 20 | $converter->convert($schema); 21 | 22 | $edgesStatements = $converter->getEdgeStatements(); 23 | $this->assertCount(7, $edgesStatements); 24 | $this->assertCount(4, $converter->getNodeStatements()); 25 | } 26 | } --------------------------------------------------------------------------------