Maintaining Open Source

Pushing a large rock up a steep hill...

Your time is not worthless

1. What makes a good

open source project?

Do something that's fun for you

Try to scratch your own itch

 Try to make it something

you can use 9-5

 Try to get company time

to work on it

2. What does a good

library look like?

Think about what makes

the code clean

amazon.com/dp/B001GSTOAM

Think about what makes

the developer experience great

$client = new Acme\Client(new GuzzleHttp\Client());

try {
    $response = $client->fetchItems([
        "limit" => 25,
        "orderBy" => ["created", "desc"],
        "filter" => [
            "owner" => "Chris",
        ],
    ]);

    if ($response["status"] === 200) {
        if (count($response["items"]) > 0) {
            // render the items...
        }
    }
} catch (GuzzleHttp\Exception\RequestException $e) {
    // handle this guzzle exception...
}
$client = new Acme\Client(new GuzzleHttp\Client());

try {
    $response = $client->fetchItems([
        "limit" => 25,
        "orderBy" => ["created", "desc"],
        "filter" => [
            "owner" => "Chris",
        ],
    ]);

    if ($response["status"] === 200) {
        if (count($response["items"]) > 0) {
            // render the items...
        }
    }
} catch (GuzzleHttp\Exception\RequestException $e) {
    // handle this guzzle exception...
}
$client = new Acme\Client(new GuzzleHttp\Client());

try {
    $response = $client->fetchItems([
        "limit" => 25,
        "orderBy" => ["created", "desc"],
        "filter" => [
            "owner" => "Chris",
        ],
    ]);

    if ($response["status"] === 200) {
        if (count($response["items"]) > 0) {
            // render the items...
        }
    }
} catch (GuzzleHttp\Exception\RequestException $e) {
    // handle this guzzle exception...
}
namespace Acme;

use GuzzleHttp\Client as GuzzleClient;

class Client {
    private $client;

    public function __construct($client = null) {
        if (is_null($client)) {
            $client = new GuzzleClient();
        }

        $this->client = $client;
    }

    // ...snip
}
<?php

$client = new Acme\Client(new GuzzleHttp\Client());

try {
    $response = $client->fetchItems([
        "limit" => 25,
        "orderBy" => ["created", "desc"],
        "filter" => [
            "owner" => "Chris",
        ],
    ]);

    if ($response["status"] === 200) {
        if (count($response["items"]) > 0) {
            // render the items...
        }
    }
} catch (GuzzleHttp\Exception\RequestException $e) {
    // handle this guzzle exception...
}
namespace Acme;

use GuzzleHttp\Client as GuzzleClient;
use Amp\Artax\DefaultClient as ArtaxClient;

class Client {
    private $client;

    public function __construct($client = null) {
        if (is_null($client)) {
            if (class_exists(GuzzleClient::class)) {
                $client = new GuzzleClient();
            }

            if (class_exists(ArtaxClient::class)) {
                $client = new ArtaxClient();
            }
        }

        $this->client = $client;
    }

    // ...snip
}
$client = new Acme\Client();

try {
    $response = $client->fetchItems([
        "limit" => 25,
        "orderBy" => ["created", "desc"],
        "filter" => [
            "owner" => "Chris",
        ],
    ]);

    if ($response["status"] === 200) {
        if (count($response["items"]) > 0) {
            // render the items...
        }
    }
} catch (GuzzleHttp\Exception\RequestException $e) {
    // handle this guzzle exception...
}

github.com/assertchris/gitstore-webflow/blob

/master/source/Clients/GuzzleClient.php

$client = new Acme\Client();

try {
    $response = $client->fetchItems([
        "limit" => 25,
        "orderBy" => ["created", "desc"],
        "filter" => [
            "owner" => "Chris",
        ],
    ]);

    if ($response["status"] === 200) {
        if (count($response["items"]) > 0) {
            // render the items...
        }
    }
} catch (GuzzleHttp\Exception\RequestException $e) {
    // handle this guzzle exception...
}
$client = new Acme\Client();

try {
    $response = $client->fetchItems([
        "limit" => 25,
        "orderBy" => ["created", "desc"],
        "filter" => [
            "owner" => "Chris",
        ],
    ]);

    if ($response["status"] === 200) {
        if (count($response["items"]) > 0) {
            // render the items...
        }
    }
} catch (GuzzleHttp\Exception\RequestException $e) {
    // handle this guzzle exception...
}
namespace Acme;

class Client {
    protected $query = [];

    public function limit(integer $limit): self {
        $this->query["limit"] = $limit;
        return $this;
    }

    public function orderBy(string $column, string $direction = "desc"): self {
        $this->query["orderBy"] = [$column, $direction];
        return $this;
    }

    public function filter(array $filters): self {
        $this->query["filter"] = $filters;
        return $this;
    }

    public function fetch() {
        return $this->fetchItems($this->query);
    }

    // ...snip
}
$client = new Acme\Client();

try {
    $response = $client
        ->limit(25)
        ->orderBy("created")
        ->filter([
            "owner" => "Chris",
        ])
        ->fetch();

    if ($response["status"] === 200) {
        if (count($response["items"]) > 0) {
            // render the items...
        }
    }
} catch (GuzzleHttp\Exception\RequestException $e) {
    // handle this guzzle exception...
}

github.com/laravel/framework/blob/master

/src/Illuminate/Support/Fluent.php

$client = new Acme\Client();

try {
    $response = $client
        ->limit(25)
        ->orderBy("created")
        ->filter([
            "owner" => "Chris",
        ])
        ->fetch();

    if ($response["status"] === 200) {
        if (count($response["items"]) > 0) {
            // render the items...
        }
    }
} catch (GuzzleHttp\Exception\RequestException $e) {
    // handle this guzzle exception...
}
$client = new Acme\Client();

try {
    $response = $client
        ->limit(25)
        ->orderBy("created")
        ->filter([
            "owner" => "Chris",
        ])
        ->fetch();

    if ($response["status"] === 200) {
        if (count($response["items"]) > 0) {
            // render the items...
        }
    }
} catch (GuzzleHttp\Exception\RequestException $e) {
    // handle this guzzle exception...
}
namespace Acme;

use Acme\Client\Response;

class Client {
    public function fetchAll() {
        $response = $this->internalFetchAll();

        return new Response(
            $response["status"] === 200,
            $response["items"],
        );
    }

    // ...snip
}
$client = new Acme\Client();

try {
    $response = $client
        ->limit(25)
        ->orderBy("created")
        ->filter([
            "owner" => "Chris",
        ])
        ->fetch();

    if ($response->isOk() && $response->hasItems()) {
        // render the items...
    }
} catch (GuzzleHttp\Exception\RequestException $e) {
    // handle this guzzle exception...
}

github.com/assertchris/gitstore-webflow/blob

/master/source/Clients/GuzzleClient.php

$client = new Acme\Client();

try {
    $response = $client
        ->limit(25)
        ->orderBy("created")
        ->filter([
            "owner" => "Chris",
        ])
        ->fetch();

    if ($response->isOk() && $response->hasItems()) {
        // render the items...
    }
} catch (GuzzleHttp\Exception\RequestException $e) {
    // handle this guzzle exception...
}
$client = new Acme\Client();

try {
    $response = $client
        ->limit(25)
        ->orderBy("created")
        ->filter([
            "owner" => "Chris",
        ])
        ->fetch();

    if ($response->isOk() && $response->hasItems()) {
        // render the items...
    }
} catch (Acme\Client\RequestException $e) {
    // handle this generic exception...
}

Write the code to be read ✓

Make the API simple ✓

Design by documentation or tests

$client = new Acme\Client(new GuzzleHttp\Client());

try {
    $response = $client->fetchItems([
        "limit" => 25,
        "orderBy" => ["created", "desc"],
        "filter" => [
            "owner" => "Chris",
        ],
    ]);

    if ($response["status"] === 200) {
        if (count($response["items"]) > 0) {
            // render the items...
        }
    }
} catch (GuzzleHttp\Exception\RequestException $e) {
    // handle this guzzle exception...
}
$client = new Acme\Client();

try {
    $response = $client
        ->limit(25)
        ->orderBy("created")
        ->filter([
            "owner" => "Chris",
        ])
        ->fetch();

    if ($response->isOk() && $response->hasItems()) {
        // render the items...
    }
} catch (Acme\Client\RequestException $e) {
    // handle this generic exception...
}

Test before or after, but do test!

Code examples help

Community files help

3. How can I profit

from my work?

You might already be doing that… 

You can take donations

 You can charge for new features

or special licensing

You can sell access to private code

Demo!

Questions?

Thanks

twitter.com/assertchris

Maintaining Open Source (July 2019)

By Christopher Pitt

Maintaining Open Source (July 2019)

Maintaining open source is a bit like being a monk. You give up your time and effort for "the greater good", and often have to deal with rudeness and entitlement along the way. It doesn't have to be like that. I want to talk to you about the how and why of creating open source code. Together, we'll look at what a good open source library looks like, and how it can pay for itself.

  • 1,006