-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathaaronfrancis.php
138 lines (116 loc) · 3.74 KB
/
aaronfrancis.php
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
<?php
/**
* Example taken and adapted from Aaron Francis "Bitmasking in Laravel and MySQL" blog post.
*
* @link https://aaronfrancis.com/2021/bitmasking-in-laravel-and-mysql-26257c0b
*/
require __DIR__.'/../vendor/autoload.php';
use Gksh\Bitmask\TinyBitmask;
enum FindMethod: int
{
case ADDRESS = 0b00000001;
case PARCEL = 0b00000010;
case GEOCODE = 0b00000100;
case DESCRIPTION = 0b00001000;
case ALTERNATE = 0b00010000;
case STREET = 0b00100000;
case SINGLE_SCRAPER = 0b01000000;
case SEARCH_SCRAPER = 0b10000000;
public function methodName(): string
{
return match ($this) {
FindMethod::PARCEL => 'attemptParcel',
FindMethod::ADDRESS => 'attemptAddress',
FindMethod::DESCRIPTION => 'attemptDescription',
FindMethod::ALTERNATE => 'attemptAlternate',
FindMethod::STREET => 'attemptStreet',
FindMethod::SINGLE_SCRAPER => 'attemptSingleScraper',
FindMethod::GEOCODE => 'attemptGeocode',
FindMethod::SEARCH_SCRAPER => 'attemptSearchScraper',
};
}
}
class FindAttempts extends TinyBitmask
{
public ?int $pid = null;
public function recordAttempt(FindMethod $method): FindAttempts
{
return $this->set($method->value);
}
public function resetAttempt(FindMethod $method): FindAttempts
{
return $this->unset($method->value);
}
public function hasAttempted(FindMethod $method): bool
{
return $this->has($method->value);
}
}
class Property // extends Model
{
public ?int $pid = null;
public FindAttempts $attempts;
public function __construct(?FindAttempts $attempts = null)
{
$this->attempts = $attempts ?? new FindAttempts();
}
public function save(): void
{
// Save the property.
dump([
'property' => $this,
'attempted' => array_map(fn (FindMethod $method) => [
$method->name => $this->attempts->hasAttempted($method),
], FindMethod::cases()),
]);
}
}
class FindIds // extends Command
{
public function handle(): void
{
foreach ($this->queryProperties() as $property) {
// Loop through the methods.
foreach (FindMethod::cases() as $method) {
// Skip ones we've already tried.
if ($property->attempts->hasAttempted($method)) {
continue;
}
// Delegate to the appropriate method.
$result = $this->{$method->methodName()}($property);
// Methods can return `false` on failure, or if they
// are currently disabled for any reason. We don't
// record an attempt, as we'll try those again.
if ($result !== false) {
$property->attempts->recordAttempt($method);
}
// Stop processing once we find the PID.
if (! is_null($property->pid)) {
break;
}
}
$property->save();
}
}
/**
* @param array<int, mixed> $arguments
*/
public function __call(string $name, array $arguments): bool
{
$key = array_rand($odds = [false, false, false, true, false], 1);
return $odds[$key];
}
/**
* @return Property[]
*/
private function queryProperties(): array
{
// Imagine this is querying the DB.
return [
new Property(FindAttempts::make()),
new Property(FindAttempts::make(FindMethod::ADDRESS->value)),
new Property(FindAttempts::make(FindMethod::PARCEL->value | FindMethod::STREET->value)),
];
}
}
(new FindIds())->handle();