├── .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 | [](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 | }
--------------------------------------------------------------------------------