aboutsummaryrefslogtreecommitdiffhomepage
path: root/Digitigrade/Model/FetchableModel.php
blob: 3efa763365c5d5676b1dbd3248ed3b5c4bfd4eb3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
<?php
namespace Digitigrade\Model;

use Digitigrade\Model;
use Digitigrade\PolicyManager;
use Digitigrade\RemoteFetchable;

abstract class FetchableModel extends Model implements RemoteFetchable {
    private static \JsonMapper $mapper;

    public static function __initStatic() {
        self::$mapper = new \JsonMapper();
        // this is unfortunately necessary to parse DateTime(Immutable)s properly
        self::$mapper->bStrictObjectTypeChecking = false;
    }

    protected static function createFromJson(\stdClass $data, bool $autoSave, ?string $uri = null): static {
        // default everything nullable to null
        $obj = new static();
        $reflProps = (new \ReflectionClass(static::class))->getProperties();
        foreach ($reflProps as $reflProp) {
            assert($reflProp instanceof \ReflectionProperty); // for intellisense lmao
            if ($reflProp->getType()->allowsNull()) {
                $obj->{$reflProp->getName()} = null;
            }
        }
        // rename 'self' to 'uri' because of reasons
        if (property_exists($data, 'self')) {
            $data->uri = $data->self;
            unset($data->self);
        }
        // map all present fields
        self::$mapper->map($data, $obj);
        // (hopefully) recursively fetch any other fetchable models contained in this one
        foreach ($reflProps as $reflProp) {
            assert($reflProp instanceof \ReflectionProperty);
            $reflType = $reflProp->getType();
            $propName = $reflProp->getName();
            if (!($reflType instanceof \ReflectionNamedType))
                continue;
            $type = $reflType->getName();
            if (is_subclass_of($type, FetchableModel::class) && isset($data->{$propName}) && is_string($data->{$propName})) {
                // try to avoid recursion
                // TODO: make a better effort at avoiding recursion (e.g. when it's more than one step nested)
                if ($data->{$propName} == $uri) {
                    $obj->{$propName} = $obj;
                } else {
                    $obj->{$propName} = $type::findByUri($data->{$propName}, $autoSave);
                }
            }
        }

        return $obj;
    }

    protected static function fetchFromRemote(string $uri, bool $autoSave): ?static {
        // fetch the object
        // janky hack: if this is an Instance then avoid recursively fetching itself
        $result = @send_request_authenticated($uri, static::class == Instance::class);
        if (!str_contains($result->headers[0], '200'))
            return null;
        $data = json_decode($result->body);
        if ($data === null)
            return null;

        if (($data->type ?? null) == 'tombstone') {
            // handle deletes
            $obj = static::findWhere('uri = ?', [$data->self]);
            if ($obj == null)
                return null;
            $obj->deleted = true;
            $obj->save();
            return null;
        }

        $obj = static::createFromJson($data, $autoSave, $uri);

        $obj->finaliseAfterFetch($uri);
        if (!$obj->validate() || !PolicyManager::getInstance()->check($obj)) {
            return null; // and don't save
        }
        if ($autoSave) {
            $obj->save();
            $obj->finaliseAfterSave();
        }

        return $obj;
    }

    protected function finaliseAfterFetch(string $uri) {
    }

    protected function finaliseAfterSave() {
    }

    public static function findByUri(string $uri, bool $autoSave = true, bool $forceRefetch = false): ?static {
        if (!$forceRefetch) {
            $obj = static::findWhere('uri = ?', [$uri]);
            if ($obj != null)
                return $obj;
        }
        return static::fetchFromRemote($uri, $autoSave);
    }
}

FetchableModel::__initStatic();