owncloud / data_exporter

Export/Import for ownCloud user data
GNU General Public License v2.0
7 stars 5 forks source link

Model changes #35

Open jvillafanez opened 5 years ago

jvillafanez commented 5 years ago

Refactor plans

Introduce OCA\DataExporter\Model\IModel

The IModel will be the base interface of all models. All valid models MUST implement this interface. The IModel interface won't have any method defined

Using this interface will allow us to create base interfaces for the extractor and importer classes, and enforce the same signature, such as:

interface Importer {
  public function import(IModel);
}
......
interface Extractor {
  public function extract(): IModel;
}

It's expected that each importer and extractor subclasses use specific models, for example:

class FileImporter extends Importer {
  public function import(File)
}
......
class FileExtractor extends Extractor {
  public function extract(): File
}

Note that the File needs to extends the IModel interface.

~Introduce OCA\DataExporter\Model\BaseCollectionModel~ (discarded)

The class BaseCollectionModel will extend the IModel interface, and it will serve as base class of collections.

Expected implementation would be something like:

abstract class BaseCollectionModel implements IModel {
    private $collection = [];
    protected abstract function getCollectionType(): string;

    public function setItems(array $items): bool {
        foreach ($items as $item) {
            if (\gettype($item) !== $this->getCollectionType()) {
                return false;
            }
        }
        $this->collection = $items;
        return true;
    }

    public function getItems(): array {
        return $this->collection;
    }
}

This means that, for example, we'll have a File and a FileCollection models. The File class will implement the IModel interface while the FileCollection will extend the BaseCollectionModel

Expected implementation for the FileCollection class should be

class FileCollection extends BaseCollectionModel {
    protected abstract function getCollectionType(): string {
        return File::class;
    }
}

The idea behind this change is to allow a strict type checking.

Folder structure changes

Model/
Model/IModel.php
Model/UserMetadata.php    (renamed from the current Metadata.php)
Model/UserMetadata/User.php
Model/UserMetadata/User/File.php
Model/UserMetadata/User/File/Version.php
Model/UserMetadata/User/Preference.php
Model/UserMetadata/User/Share.php

The "big" change would be to rename the current Metadata.php to UserMetadata, and move almost all the current classes inside the folder. This should reflect the dependecies among the models

IljaN commented 5 years ago

Collections: While this change might be the right thing to do from the object oriented design standpoint and might slightly improve maintainability I don't see any practical benefit this gives us.

I am not completely opposed to this, but I think we should get more functionality going before thinking of further refactorings. Also at the moment I think we don't know if our architecture is 100% valid as more and more requirements trickle in and we find new edge-case. So as long as code is changed a lot we should not in-grain types too deep too early.

Other changes make sense.

jvillafanez commented 5 years ago

Yes. There are additional problems to solve with the symfony's serializer if we decide to use collections. The changes will be bigger than the ones sketched.

So, in exchange of dismissing the collections:

For the extractors, we'll likely need them to return some kind of model iterator. The idea will be something similar to the collections but outside of the models. This means that, even if the extractor is expected to return only one object (the UserExtractor for example), it will be required to return an iterator over that element. I'm still thinking how to do this.

jvillafanez commented 5 years ago

For the extractors, we'll likely need them to return some kind of model iterator.

This is quite flexible. The extractor interface will return an iterator; other extractors can return a Generator, an ArrayIterator (for the common case), or even a custom iterator (mainly for proper type hinting, but this shouldn't be needed)

jvillafanez commented 5 years ago

For the Version model, it will only contain the path where the version is located. The specific path shouldn't matter.

The File model will have a list of Versions. This list might be empty. Note that the list of versions shouldn't contain the actual file (or latest version that should be shown to the user).

{
  "type": "file",
  "path": "my/file.txt",
  "eTag": "1234567890",
  "permissions": 31,
  "versions" : [
    {"path": "my/file.txt.v01"},
    {"path": "my/file.txt.v02"},
    {"path": "my/file.txt.v03"},
  ]
}

Note that the list of versions will be processed as they're defined. For the example above, we'll write first the v01, then the v02, then the v03 and finally the actual file.

To further clarify, this is also valid:

{
  "type": "file",
  "path": "my/file.txt",
  "eTag": "1234567890",
  "permissions": 31,
  "versions" : [
    {"path": "versions/my/v01.txt"},
    {"path": "versions/my/v40.txt"},
    {"path": "my/file.txt.v03"},
  ]
}
IljaN commented 5 years ago

This is something where I already stumbled while thinking about comments which also belong to a file: With the current architecture we have specialized extractors. Now if we nest it like this the files-extractor/importer will need to know about versions and potentially comments.

jvillafanez commented 5 years ago

This is something where I already stumbled while thinking about comments which also belong to a file: With the current architecture we have specialized extractors. Now if we nest it like this the files-extractor/importer will need to know about versions and potentially comments.

Not really. The file extractor can depend on a version extractor and a comment extractor to take care of fetching the versions and comments. Something similar can be done for the importer.

jvillafanez commented 5 years ago

parent-child model relationship

As said above, the plan is to have a import(IModel $model) method. This implies:

The VersionImporter would look like:

public function import(Version $version) { .... }

According to the model, we need some file model information (the file path) to know where the versions should be placed, or what is the file associated to the versions.

In order to deal with this we'll need some additional changes: