aboutsummaryrefslogtreecommitdiffhomepage
path: root/Digitigrade/Model/FetchableModel.php
blob: 30b68e7dbd89eabd0f78d901c1acf0beb4c7c72a (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
<?php
namespace Digitigrade\Model;

use Digitigrade\Model;
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;
    }

    private static function fetchFromRemote(string $uri, bool $autoSave): ?static {
        // fetch the object
        $data = @file_get_contents($uri, context: stream_context_create(['http' => [
            'header' => 'Accept: application/json'
        ]]));
        if ($data === false)
            return null;
        $data = json_decode($data);
        if ($data === null)
            return null;
        // 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) && 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);
                }
            }
        }

        if ($autoSave) {
            $obj->save();
        }

        return $obj;
    }

    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();