Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

JsonLd Implementation #1551

Closed
JohnnyMcWeed opened this issue Oct 13, 2017 · 27 comments
Closed

JsonLd Implementation #1551

JohnnyMcWeed opened this issue Oct 13, 2017 · 27 comments
Assignees
Milestone

Comments

@JohnnyMcWeed
Copy link
Contributor

JohnnyMcWeed commented Oct 13, 2017

Hey

As this part is not implemented yet, I wanted to ask you what you're thinking about the following idea:
Using objects for the schema things and methods to build them, then through JsonLd we can register them. As we can't extend classes, we could use interfaces to make sure all properties are implemented to use.
What do you think?

Had some time to make an example to explain it better ;)
Here is a working example: https://github.com/JohnnyMcWeed/luya/tree/JsonLd/core/web
Could be used like this:

  • \luya\web\JsonLd::addGraph( (new \luya\web\jsonld\Person())->name("Max")->toArray() );
  • \luya\web\JsonLd::addGraph( (new \luya\web\jsonld\Organisation("LuyaDev"))->toArray() );
  • \luya\web\JsonLd::addGraph( (new \luya\web\jsonld\Organisation("Others") )->member( (new \luya\web\jsonld\Person("Moritz"))->toArray() )->toArray());
@nadar nadar self-assigned this Oct 14, 2017
@nadar
Copy link
Contributor

nadar commented Oct 14, 2017

in general: i think the jsonld implementation is very strong argument for using luya, therefore this should be as less pain as possible for developers to integrate.

Your solution is very close to what i think should be done.

General Element

shortform:

$person = new Person(['firstname' => 'John', 'lastname' => 'Doe', 'email' => 'john@luya.io']);

which is equals to:

$person = (new Person())
    ->firstname('John')
    ->lastname('Doe')
    ->email('john@luya.io');

All jsonld items should contain

  • yii\base\Object (getter & setter ability)
  • yii\base\Arrayable The arrayble defintion
  • yii\base\ArrayableTrait The converter

There for we need a base class like

  • luya\jsonld\BaseElement (or sth. like this) which is an abstract class.

Chaining

A very powerfull option is chaining elements, therefore the element can return the available chain objects (assuming an event can have locations):

class Event extends BaseElement
{
    // general event attributes ...

    private $_locations = [];

    // as now $this->locations is an arrayable element, from baseElement 
   // the group with the attribute key 'locations' should be build correctly.
    public function setLocation(array $options = [])
    {
        $this->_locations[] = return new Location($options);
    }

    public function setLocations(array $locations)
    {
        foreach ($locations as $locationData) {
             $this->setLocation($locationData);
        }
    }

    public function getLocations()
    {
        return $this->_locations;
    }

    // to array implementation would be then:

   public function fields()
   {
       return ['locations'];
   }
}

So a full example could look like this:

$event = new Event();
$event->endDate(strtotime("+3 weeks"));
$event->location()
    ->address('Bahnhofstrasse 27')
   ->zip(5000)
   ->city('Zürich');

or as object array config notation:

$event = new Event([
    'endDate' => strtotime('+3 weeks'),
    'location' => [
        'address' => 'Bahnhofstrasse 27',
        'zip' => 5000,
        'city' => 'Zürich'
    ]
]);

What do you think?

@JohnnyMcWeed
Copy link
Contributor Author

Okay, I can give it a try in the evening / tomorrow ;)

@nadar
Copy link
Contributor

nadar commented Oct 14, 2017

@JohnnyMcWeed i just updated the chaining text a bit in order to show the different usage types.

@JohnnyMcWeed
Copy link
Contributor Author

JohnnyMcWeed commented Oct 15, 2017

I changed the code (https://github.com/JohnnyMcWeed/luya/tree/JsonLd/core)
(Added class luya\base\JsonLdBaseElement)

Can be used like this (All pass https://search.google.com/structured-data/testing-tool):
JsonLd::addGraph((new Thing())->setName("the thing")->toArray());

JsonLd::addGraph((new Thing(["name"=>"THE THING"]))->toArray());

JsonLd::addGraph((new Person())->setName("max")->toArray());

JsonLd::addGraph((new Person(["name"=>"MAX"]))->toArray());

JsonLd::addGraph((new Person())->setName("max")->setChildren(["name" => "anne"])->toArray());

JsonLd::addGraph((new Person(["name"=>"MAX", "children"=>["name"=>"ANNE"]]))->toArray());

JsonLd::addGraph((new Person())->setName("max")->setChildren(["name" => "anne"])->setChildren(["name" => "lisa"])->toArray());

JsonLd::addGraph((new Person("name"=>"Max"))->setChildren([["name" => "anne"],["name"=>"lisa"]])->toArray());

JsonLd::addGraph((new Person(["name"=>"MAX", "children"=>[["name"=>"ANNE"],["name"=>"LISA"]]]))->toArray());

What do you think about it?

@nadar
Copy link
Contributor

nadar commented Oct 15, 2017

@JohnnyMcWeed toArray should be invoken by addGraph.

Which should then auto call toArray inside json::encode function: https://github.com/yiisoft/yii2/blob/master/framework/helpers/BaseJson.php#L156

@nadar
Copy link
Contributor

nadar commented Oct 15, 2017

General Inputs:

So maybe we should just use:

$event = new Event();
$event->setLocation()
    ->address('Bahnhofstrasse 27')
   ->zip(5000);
$event->setLocation()
    ->address('Another Street')
   ->zip(4600);

@nadar
Copy link
Contributor

nadar commented Oct 15, 2017

Instead uf override default logic (https://github.com/JohnnyMcWeed/luya/blob/JsonLd/core/base/JsonLdBaseElement.php#L90) why not just return the fields array from arrayable interface?

should i make a full example? I think we are very close to a good solution!

(btw: here some luya coding rules: https://luya.io/guide/luya-guideline using psr2, etc.)

@JohnnyMcWeed
Copy link
Contributor Author

So probably misunderstood what you wanted to have. Adjusted the chain a little bit.

Before we had a chain on the same element so we could go through it's properties, but not down to newly created child elements. Now the chain takes the newly created objects as return so that they can be filled. Moreover you can set switches to go back to the parent element to set more information.
Person->setChild is a working example

Changed context to get merged in at the end.
toArray() is now invoked at the end.
toArray() is back to default (Missed something in the docs. Now changed accordingly so that it works with default)

Do you mean something like that?
Example 1
JsonLd::addGraph((new Person())->setName("Max")->setChild([],true)->setName("Anna")->setChild()->setName("Fritz")->back()->setChild()->setName("Jasmin"));
Example 2
JsonLd::addGraph((new Person())->setName("Max")->setChild([],true)->setName("Anna")->setChild()->setName("Fritz")->back(1)->setChild()->setName("Jasmin"));
Example 3
JsonLd::addGraph((new Person())->setName("Max")->setChild([],true)->setName("Anna")->setChild()->setName("Fritz")->back('',true)->setChild()->setName("Jasmin"));

Example 1: Anna has 2 children
Example 2&3: Max has 2 children

So back to your example, we can adjust it so it ends up as something similar to this (or as you mentioned):

$event = new Event();
$event->setLocation()
    ->address('Bahnhofstrasse 27')
    ->zip(5000)
    ->back()
    ->setLocation()
    ->address('Another Street')
    ->zip(4600);

https://github.com/JohnnyMcWeed/luya/tree/JsonLd/core

@nadar
Copy link
Contributor

nadar commented Oct 17, 2017

First of all, thank for taking time helping make luya better!

I think you chaining example is far to complex, and hard to understand (with back() function). Of course at some point, chaining does not allow you to get back, but understanding the mechanism you can always go back by using the variabled object:

$event = new Event();

$event->setLocation()
    ->address('Bahnhofstrasse 27')
   ->zip(5000);

$event->setLocation()
    ->address('Another Street')
   ->zip(4600);

so $event is variable you can always "go back".

I just pushed a commit with the basic implementation and also an unit test, you can now use this as your basic setup and also directly add unit tests

@JohnnyMcWeed
Copy link
Contributor Author

Hmm... I don't really think it was hard to understand as long as it is well explained, but it enhances the possibility way more on how to use it.

->back()     go back to last switch
->back(int)     go back and pass "int" switches
->back([],true)     go back to base element

So finally the main difference is that in your case you must have a variabled object, in mine you don't need this. So it's only more flexibility I think.

Other point we need to check: How do you want to pass variabled object?
setEvent($event->toArray()) or setEvent($event)
At the moment, the function would be similar to this, so "toArray()" is needed I think:

function setEvent(array $config = []) {
    $event = new Event($config);
    $this->events[] = $event;
    return $event;
}

@nadar
Copy link
Contributor

nadar commented Oct 17, 2017

Maybe we should not use chaining then and make this possible:

$event = new Event();
$location = new Location();
$address1 = new Address();
$address2 = new Address();

// new we have all objects and pass all child infos.
$location->setAddress($address1);
$location->setAddress($address2);
$event->setLocation($location);

this way we do not mix up things, and have a straight forward implementation.

@JohnnyMcWeed
Copy link
Contributor Author

Hmm...

Or let's change the function to something like this:

function setEvent($config = []) 
{
    if ($config instanceof Event) {
        $this->events[] = $config;
        return $config;    
    } else {
        $event = new Event($config);
        $this->events[] = $event;
        return $event;    
    }
}

@nadar
Copy link
Contributor

nadar commented Oct 17, 2017

Maybe its better to start simple and just allow to the setter for the given object, this is pretty straight forward implementation and also very readable api.

public function setEvent(Event $event)
{
    $this->_events[] = $event;
}

It will also not bloat our classes with if/else codes and follows more the yii 2 philosophy with getter and setters

@JohnnyMcWeed
Copy link
Contributor Author

The reason why I came up with my switches solution:
If we now place functions similar to the one I did before we can have complete functionality in I think most kind of situation.
Either set up all objects on it's own like you did before and then combine them and pass them to the graph, or go straight forward and make everything on the fly.

@nadar
Copy link
Contributor

nadar commented Oct 17, 2017

Let's start with the straight forward solution i made, when can then still add a helper class for making complex chainings. Like

JsonLd::chain()->event()
-> etc.
-> etc.

so we have a small a maintainable code base but can extend dynamic.

Why using the straight forward implementation:

  • There will be so much objects and "nesting" options, we should keep the classes small and maintainable and not full of logic.
  • DRY
  • Readable API without explanations
  • Real world examples will fit our solution when it comes to work with data from a database, working with foreaches etc. chaning would not work anyhow.

@nadar nadar added this to the 1.x milestone Oct 17, 2017
@JohnnyMcWeed
Copy link
Contributor Author

Sounds good to me.

What do you think, should I do it with traits?
Because f.e. LocalBusiness (http://schema.org/LocalBusiness) extends Organisation and Place

@nadar
Copy link
Contributor

nadar commented Oct 17, 2017

So you mean Organisation and Place would be a trait?

Yes good idea, but the trait only contains the getter and setter methods and not the fields defintion.

trait PlaceTrait
{
    setXYZ($value);

   getXYZ();
}

class Place extends BaseGraphElement
{
    use PlaceTrait;

    public function fields()
    {
         return ['xyz'];
    }
}

I also think we should rename BaseGraphElement to what you introduced BaseThing - base stands for an abstraction and thing is the right terminology from schema.org.

Just delete your luya repo and fork again, so you can start straight from my code and send a first PR reference to this issue.

@JohnnyMcWeed
Copy link
Contributor Author

All methods as traits so they can be reused was my idea. All methods will be stored in traits which are called like the things. Something like that:

  • OrganisationTrait stores all methods for Organisation.
  • -> Organisation use OrganisationTrait
  • PlaceTrait stores all methods for Place
  • -> Place use PlaceTrait
  • LocalBusinessTrait stores all methods for Local business
  • -> LocalBusiness extends Organisation use LocalBusinessTrait, PlaceTrait

I'll leave this branch as it is now.. maybe there's something reusable for the chain helper... Update the repo and start at you point ;)

@nadar
Copy link
Contributor

nadar commented Oct 17, 2017

Ok, as long as the traits only contains the getter/setter methods and not the fields() defintion.

btw: maybe BaseThing could contain some extra variables/methods which have to ensure the @type properties.

@JohnnyMcWeed
Copy link
Contributor Author

I'll continue in about two weeks ;)

What were you thinking about with the following:

some extra variables/methods which have to ensure the @type properties

An array with all elements and a check with "in_array()"?

@nadar
Copy link
Contributor

nadar commented Oct 18, 2017

I was thinking about how to implement

@context and @type etc. (https://github.com/luyadev/luya/blob/master/core/web/JsonLd.php#L37-L38)

i think @context could be statically added when passing the data to the view, somewhere here: https://github.com/luyadev/luya/blob/master/core/web/JsonLd.php#L109

Maybe i have time to start already with the basics until then, as we would like to introduce the "final" api until 12.12.2017 und release version 1.0.0

@nadar nadar modified the milestones: 1.x, 1.0.0 Oct 18, 2017
@JohnnyMcWeed
Copy link
Contributor Author

Alright :)

In my last example I did an array_merge($objectArray, ["@context" => "http://schema.org"]), which was working properly.

I think @type as property was good before, as it changes with all types. So if you'd like we can make a check if that property is in an array with all possible objects. But I'm not sure if this is really needed, as the user of this class doesn't have to touch that property at all as it is preset by the object.

@JohnnyMcWeed
Copy link
Contributor Author

Moreover @type could be constants I think....

@nadar
Copy link
Contributor

nadar commented Oct 18, 2017

If every BaseThing must implement a type (which i don't know, but i assume this is the case) we could make an abstract method in BaseThing getType(). And using constants for types is good idea! Those constants should be defined in BaseThing like TYPE_BUSINESS etc.

@nadar
Copy link
Contributor

nadar commented Oct 21, 2017

@JohnnyMcWeed i implemented the basic json ld usage and tested with unit tests. So now we can proceed implementing traits, things and constants.

@nadar
Copy link
Contributor

nadar commented Oct 24, 2017

Maybe its a good idea to use an interface for things, for example assume we would have a PersonInterface which defines the getter methods required for a Person, like getFirstname() etc. It would name be very easy to implement this interface to an existing active record model, afterwards it would be possible to just set this model into the json ld class:

$model = PersonModel::findOne(1);

JsonLd::person($model);

otherwise you have to bind those values like:

$model = PersonModel::findOne(1);

JsonLd::person(['firstname' => $model->firstname]); // ... etc.

(just thinking loud ;-))

@JohnnyMcWeed JohnnyMcWeed mentioned this issue Nov 14, 2017
@nadar
Copy link
Contributor

nadar commented Nov 21, 2017

Progress looks good, i think only 2 things are missing:

  • types like URL should be defined, and required in setter methdos. setUrl(Url $url) or we wont introduce until 1.1.0
  • i am going to remove null values from toArray method, looks just wrong and ugly ;-)

nadar added a commit that referenced this issue Nov 28, 2017
@nadar nadar mentioned this issue Nov 28, 2017
nadar added a commit that referenced this issue Nov 28, 2017
@nadar nadar closed this as completed in 8d1387d Dec 11, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants