{"action":"created","comment":{"url":"https://api.github.com/repos/Expensify/Bedrock-PHP/pulls/comments/141123688","pull_request_review_id":65291391,"id":141123688,"diff_hunk":"@@ -218,247 +305,232 @@ public function call($method, $headers = [], $body = '')\n     }\n \n     /**\n-     * What is the last commit count of the node we talked to.\n-     *\n-     * This is used to ensure if we make a subsequent request to a different\n-     * node in the same session, that the node waits until it is at least as\n-     * \"fresh\" as the node we originally queried.\n-     *\n-     *  @var null|string\n-     */\n-    private $commitCount = null;\n-\n-    /**\n-     * Existing socket, if any.  It's reused each time, if possible.\n-     *\n-     *  @var null|resource\n-     */\n-    private $socket = null;\n-\n-    /**\n-     * The PID that opened this socket.  This class is designed to be used in a\n-     * script that forks, so we need to avoid multiple processes using the same\n-     * socket by accident.\n-     *\n-     *  @var null|int\n-     */\n-    private $socketPID = null;\n-\n-    /**\n-     * Hostname we attempt to connect to first.\n-     *\n-     *  @var null|string\n-     */\n-    private $host = null;\n-\n-    /**\n-     * Port we attempt to connect to first.\n+     * Sends the request on a new socket, if a previous one existed, it closes the connection first.\n      *\n-     *  @var null|int\n-     */\n-    private $port = null;\n-\n-    /**\n-     * Hostname we attempt if the first didn't work.\n-     *\n-     *  @var null|string\n-     */\n-    private $failoverHost = null;\n-\n-    /**\n-     * Port we attempt if the first didn't work.\n-     *\n-     *  @var null|int\n-     */\n-    private $failoverPort = null;\n-\n-    /**\n-     * @var array\n-     */\n-    private static $config = [];\n-\n-    /**\n-     * @var LoggerInterface\n-     */\n-    private $logger;\n-\n-    /**\n-     * @var StatsInterface\n-     */\n-    private $stats;\n-\n-    /**\n-     * @var string The bedrock write consistency we want to use.\n+     * @throws ConnectionFailure When the failure is before sending any data to the server\n+     * @throws BedrockError When we already sent some data\n      */\n-    private $writeConsistency;\n-\n-    /**\n-     * Create and connect a socket (with failover).\n-     */\n-    private function reconnect()\n+    private function sendRawRequest(string $host, int $port, string $rawRequest)\n     {\n-        // If we already have a socket and our PID hasn't changed (eg, we haven't forked), use that\n-        if ($this->socket && $this->socketPID == posix_getpid()) {\n-            $this->getLogger()->debug('Reusing existing socket');\n-\n-            return;\n-        }\n-        $this->getLogger()->debug('Opening new socket');\n-\n+        $this->logger->info('Bedrock\\Client - Opening new socket', ['host' => $host]);\n         // Try to connect to the requested host\n-        $this->socketPID = posix_getpid();\n+        if ($this->socket) {\n+            socket_close($this->socket);\n+            $this->socket = null;\n+        }\n         $this->socket = @socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp'));\n \n         // Make sure we succeed to create a socket\n         if ($this->socket === false) {\n             $socketError = socket_strerror(socket_last_error());\n-            throw new ConnectionFailure(\"Could not connect to Bedrock primary or failover $socketError\");\n+            throw new ConnectionFailure(\"Could not connect to create socket: $socketError\");\n         }\n \n-        // Configure this socket and connect to the primary host, failing over if necessary\n-        socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, ['sec' => $this->sendTimeout, 'usec' => 0]);\n+        // Configure this socket and try to connect to it\n+        socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, ['sec' => $this->connectionTimeout, 'usec' => 0]);\n         socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, ['sec' => $this->readTimeout, 'usec' => 0]);\n-        if (!(@socket_connect($this->socket, $this->host, $this->port))) {\n-            // Connection to the primary host failed; try the failover\n-            $socketError = socket_strerror(socket_last_error($this->socket));\n-            $this->getLogger()->info(\"Failed to connect to '{$this->host}' ($socketError), trying failover...\");\n-            if (!(@socket_connect($this->socket, $this->failoverHost, $this->failoverPort))) {\n-                // Connection to both the main host and the failover failed!\n-                $socketError = socket_strerror(socket_last_error($this->socket));\n-                throw new ConnectionFailure(\"Could not connect to Bedrock primary host ({$this->host}) or failover ({$this->failoverHost}): $socketError\");\n-            }\n+        @socket_connect($this->socket, $host, $port);\n+        $socketErrorCode = socket_last_error($this->socket);\n+        socket_clear_error($this->socket);\n+        if ($socketErrorCode) {\n+            $socketError = socket_strerror($socketErrorCode);\n+            throw new ConnectionFailure(\"Could not connect to Bedrock host $host:$port. Error: $socketErrorCode $socketError\");\n+        }\n+\n+        // Send the information to the socket\n+        $bytesSent = socket_send($this->socket, $rawRequest, strlen($rawRequest), MSG_EOF);\n+\n+        // Failed to send anything\n+        if ($bytesSent === false) {\n+            $socketErrorCode = socket_last_error();\n+            $socketError  = socket_strerror($socketErrorCode);\n+            throw new ConnectionFailure(\"Failed to send request to bedrock host $host:$port. Error: $socketErrorCode $socketError\");\n+        }\n+\n+        // We sent something; can't retry or else we might double-send the same request. Let's make sure we sent the\n+        // whole thing, else there's a problem.\n+        if ($bytesSent != strlen($rawRequest)) {\n+            $this->logger->info('Bedrock\\Client - Could not send the whole request', ['bytesSent' => $bytesSent, 'expected' => strlen($rawRequest)]);\n+            throw new BedrockError(\"Sent partial request to bedrock host $host:$port\");\n         }\n     }\n \n-    /**\n-     * Sends the request on the existing socket, if possible, else it reconnects.\n-     *\n-     * @param string $rawRequest\n-     *\n-     * @throws BedrockError\n-     */\n-    private function sendRawRequest($rawRequest)\n+    private function getPossibleHosts()\n     {\n-        // Try up to 3 times, reconnecting each time\n-        $attempts = 0;\n-        while (++$attempts < 3) {\n-            // Reconnect (if necessary) and send\n-            $this->reconnect();\n-            $bytesSent = socket_send($this->socket, $rawRequest, strlen($rawRequest), MSG_EOF);\n-            if ($bytesSent === false) {\n-                // Failed to send anything, let's try again (if we haven't exceeded our retry limit)\n-                $errorCode = socket_last_error();\n-                $errorMsg  = socket_strerror($errorCode);\n-                $this->getLogger()->warning('Bedrock socket_send error', [\n-                    'code' => $errorCode,\n-                    'msg' => $errorMsg,\n-                    'attempts' => $attempts,\n-                ]);\n-\n-                // Free the socket on the system\n-                socket_close($this->socket);\n-                $this->socket = null;\n-            } else {\n-                // We sent something; can't retry else we might double-send the same request.  Let's make sure we sent the\n-                // whole thing, else there's a problem.\n-                if ($bytesSent == strlen($rawRequest)) {\n-                    // Cool, we sent the whole thing.\n-                    return;\n-                } else {\n-                    // Partial send -- abort\n-                    throw new BedrockError('Sent partial request to Bedrock, aborting.');\n-                }\n+        // We get the host configs from the APC cache. Then, we check the configuration there with the passed\n+        // configuration and if it's outdated (ie: it has different hosts from the one in the config), we reset it. This\n+        // is so that we don't keep the old cache after changing the hosts or failover configuration.\n+        if ((!defined('TRAVIS_RUNNING') || !TRAVIS_RUNNING)) {\n+            $apcuKey = self::APCU_CACHE_PREFIX.$this->clusterName;\n+            $cachedHostConfigs = apcu_fetch($apcuKey) ?: [];\n+            $this->logger->info('Bedrock\\Client - APC fetch host configs', $cachedHostConfigs);\n+\n+            // If the hosts and ports in the cache don't match the ones in the config, reset the cache.\n+            $cachedHostsAndPorts = [];\n+            foreach ($cachedHostConfigs as $hostName => $config) {\n+                $cachedHostsAndPorts[$hostName] = $config['port'];\n+            }\n+            asort($cachedHostsAndPorts);\n+            $uncachedHostsAndPort = [];\n+            foreach (array_merge($this->mainHostConfigs, $this->failoverHostConfigs) as $hostName => $config) {\n+                $uncachedHostsAndPort[$hostName] = $config['port'];\n+            }\n+            asort($uncachedHostsAndPort);\n+            if ($cachedHostsAndPorts !== $uncachedHostsAndPort) {\n+                $cachedHostConfigs = array_merge($this->mainHostConfigs, $this->failoverHostConfigs);\n+                $this->logger->info('Bedrock\\Client - APC init host configs', $cachedHostConfigs);\n+                apcu_store($apcuKey, $cachedHostConfigs);\n+            }\n+        } else {\n+            $cachedHostConfigs = array_merge($this->mainHostConfigs, $this->failoverHostConfigs);\n+        }\n+\n+        // Get one main host and all the failovers, then remove any of them that we know already failed.\n+        // Assemble the list of servers we'll try, in order.  First, pick one of the main hosts. We pick randomly\n+        // because we want to equally balance each server across all of its local databases. This allows us to have an\n+        // unequal number of servers and databases in a given datacenter. Also, we only pick one (versus trying both)\n+        // because if our first attempt fails we want to equally balance across *all* databases -- including the remote\n+        // ones. Otherwise if a database node goes down, the other databases in the same datacenter would get more load\n+        // (whereas this approach ensures the load is spread evenly across all).\n+        $failoverHostNames = array_keys($this->failoverHostConfigs);\n+        shuffle($failoverHostNames);\n+        $mainHostName = array_rand($this->mainHostConfigs);\n+        $hostNames = array_merge([$mainHostName], $failoverHostNames);\n+\n+        $nonBlackListedHosts = [];\n+        foreach ($hostNames as $hostName) {\n+            $blackListedUntil = $cachedHostConfigs[$hostName]['blacklistedUntil'] ?? null;\n+            if (!$blackListedUntil || $blackListedUntil < time()) {\n+                $nonBlackListedHosts[$hostName] = $cachedHostConfigs[$hostName];","path":"src/Client.php","position":574,"original_position":574,"commit_id":"6a8fd0ca645fc7978d61c8ccfcb338291ffe668b","original_commit_id":"6a8fd0ca645fc7978d61c8ccfcb338291ffe668b","user":{"login":"quinthar","id":575186,"avatar_url":"https://avatars3.githubusercontent.com/u/575186?v=4","gravatar_id":"","url":"https://api.github.com/users/quinthar","html_url":"https://github.com/quinthar","followers_url":"https://api.github.com/users/quinthar/followers","following_url":"https://api.github.com/users/quinthar/following{/other_user}","gists_url":"https://api.github.com/users/quinthar/gists{/gist_id}","starred_url":"https://api.github.com/users/quinthar/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/quinthar/subscriptions","organizations_url":"https://api.github.com/users/quinthar/orgs","repos_url":"https://api.github.com/users/quinthar/repos","events_url":"https://api.github.com/users/quinthar/events{/privacy}","received_events_url":"https://api.github.com/users/quinthar/received_events","type":"User","site_admin":false},"body":"Do PHP associative arrays maintain order?  Assuming it's not blacklisted, we want to try `$mainHostName` first.  And we do in fact insert that into `$nonBlackListedHosts` first.  But is this guaranteed to be returned first from the PHP array?  Asked another way, if you do this:\r\n\r\n```\r\n$foo['a'] = 1;\r\n$foo['b'] = 2;\r\nreset($foo);\r\n$first = key($foo);\r\n```\r\n\r\nWill `$first` *always* be `a`?","created_at":"2017-09-26T17:18:48Z","updated_at":"2017-09-26T17:21:31Z","html_url":"https://github.com/Expensify/Bedrock-PHP/pull/41#discussion_r141123688","pull_request_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/pulls/41","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/Expensify/Bedrock-PHP/pulls/comments/141123688"},"html":{"href":"https://github.com/Expensify/Bedrock-PHP/pull/41#discussion_r141123688"},"pull_request":{"href":"https://api.github.com/repos/Expensify/Bedrock-PHP/pulls/41"}}},"pull_request":{"url":"https://api.github.com/repos/Expensify/Bedrock-PHP/pulls/41","id":142342865,"html_url":"https://github.com/Expensify/Bedrock-PHP/pull/41","diff_url":"https://github.com/Expensify/Bedrock-PHP/pull/41.diff","patch_url":"https://github.com/Expensify/Bedrock-PHP/pull/41.patch","issue_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/issues/41","number":41,"state":"open","locked":false,"title":"Improved failover code","user":{"login":"iwiznia","id":521248,"avatar_url":"https://avatars1.githubusercontent.com/u/521248?v=4","gravatar_id":"","url":"https://api.github.com/users/iwiznia","html_url":"https://github.com/iwiznia","followers_url":"https://api.github.com/users/iwiznia/followers","following_url":"https://api.github.com/users/iwiznia/following{/other_user}","gists_url":"https://api.github.com/users/iwiznia/gists{/gist_id}","starred_url":"https://api.github.com/users/iwiznia/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/iwiznia/subscriptions","organizations_url":"https://api.github.com/users/iwiznia/orgs","repos_url":"https://api.github.com/users/iwiznia/repos","events_url":"https://api.github.com/users/iwiznia/events{/privacy}","received_events_url":"https://api.github.com/users/iwiznia/received_events","type":"User","site_admin":false},"body":"@flodnv please review. /cc @quinthar \r\n\r\nThis implements the new failover code we talked about in https://github.com/Expensify/Expensify/issues/54343\r\n\r\nSee all tests in the [web PR](https://github.com/Expensify/Web-Expensify/pull/18557).","created_at":"2017-09-21T13:23:04Z","updated_at":"2017-09-26T17:21:31Z","closed_at":null,"merged_at":null,"merge_commit_sha":"581e6de0245b5f7979aaf26f5f86e0876b21a7d5","assignee":{"login":"iwiznia","id":521248,"avatar_url":"https://avatars1.githubusercontent.com/u/521248?v=4","gravatar_id":"","url":"https://api.github.com/users/iwiznia","html_url":"https://github.com/iwiznia","followers_url":"https://api.github.com/users/iwiznia/followers","following_url":"https://api.github.com/users/iwiznia/following{/other_user}","gists_url":"https://api.github.com/users/iwiznia/gists{/gist_id}","starred_url":"https://api.github.com/users/iwiznia/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/iwiznia/subscriptions","organizations_url":"https://api.github.com/users/iwiznia/orgs","repos_url":"https://api.github.com/users/iwiznia/repos","events_url":"https://api.github.com/users/iwiznia/events{/privacy}","received_events_url":"https://api.github.com/users/iwiznia/received_events","type":"User","site_admin":false},"assignees":[{"login":"iwiznia","id":521248,"avatar_url":"https://avatars1.githubusercontent.com/u/521248?v=4","gravatar_id":"","url":"https://api.github.com/users/iwiznia","html_url":"https://github.com/iwiznia","followers_url":"https://api.github.com/users/iwiznia/followers","following_url":"https://api.github.com/users/iwiznia/following{/other_user}","gists_url":"https://api.github.com/users/iwiznia/gists{/gist_id}","starred_url":"https://api.github.com/users/iwiznia/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/iwiznia/subscriptions","organizations_url":"https://api.github.com/users/iwiznia/orgs","repos_url":"https://api.github.com/users/iwiznia/repos","events_url":"https://api.github.com/users/iwiznia/events{/privacy}","received_events_url":"https://api.github.com/users/iwiznia/received_events","type":"User","site_admin":false}],"requested_reviewers":[],"milestone":null,"commits_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/pulls/41/commits","review_comments_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/pulls/41/comments","review_comment_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/issues/41/comments","statuses_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/statuses/6a8fd0ca645fc7978d61c8ccfcb338291ffe668b","head":{"label":"Expensify:ionatan_failovers","ref":"ionatan_failovers","sha":"6a8fd0ca645fc7978d61c8ccfcb338291ffe668b","user":{"login":"Expensify","id":476779,"avatar_url":"https://avatars0.githubusercontent.com/u/476779?v=4","gravatar_id":"","url":"https://api.github.com/users/Expensify","html_url":"https://github.com/Expensify","followers_url":"https://api.github.com/users/Expensify/followers","following_url":"https://api.github.com/users/Expensify/following{/other_user}","gists_url":"https://api.github.com/users/Expensify/gists{/gist_id}","starred_url":"https://api.github.com/users/Expensify/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Expensify/subscriptions","organizations_url":"https://api.github.com/users/Expensify/orgs","repos_url":"https://api.github.com/users/Expensify/repos","events_url":"https://api.github.com/users/Expensify/events{/privacy}","received_events_url":"https://api.github.com/users/Expensify/received_events","type":"Organization","site_admin":false},"repo":{"id":68838259,"name":"Bedrock-PHP","full_name":"Expensify/Bedrock-PHP","owner":{"login":"Expensify","id":476779,"avatar_url":"https://avatars0.githubusercontent.com/u/476779?v=4","gravatar_id":"","url":"https://api.github.com/users/Expensify","html_url":"https://github.com/Expensify","followers_url":"https://api.github.com/users/Expensify/followers","following_url":"https://api.github.com/users/Expensify/following{/other_user}","gists_url":"https://api.github.com/users/Expensify/gists{/gist_id}","starred_url":"https://api.github.com/users/Expensify/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Expensify/subscriptions","organizations_url":"https://api.github.com/users/Expensify/orgs","repos_url":"https://api.github.com/users/Expensify/repos","events_url":"https://api.github.com/users/Expensify/events{/privacy}","received_events_url":"https://api.github.com/users/Expensify/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Expensify/Bedrock-PHP","description":null,"fork":false,"url":"https://api.github.com/repos/Expensify/Bedrock-PHP","forks_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/forks","keys_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/teams","hooks_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/hooks","issue_events_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/issues/events{/number}","events_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/events","assignees_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/assignees{/user}","branches_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/branches{/branch}","tags_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/tags","blobs_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/git/refs{/sha}","trees_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/statuses/{sha}","languages_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/languages","stargazers_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/stargazers","contributors_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/contributors","subscribers_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/subscribers","subscription_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/subscription","commits_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/commits{/sha}","git_commits_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/git/commits{/sha}","comments_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/comments{/number}","issue_comment_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/issues/comments{/number}","contents_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/contents/{+path}","compare_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/merges","archive_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/downloads","issues_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/issues{/number}","pulls_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/pulls{/number}","milestones_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/milestones{/number}","notifications_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/labels{/name}","releases_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/releases{/id}","deployments_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/deployments","created_at":"2016-09-21T16:52:46Z","updated_at":"2017-04-13T00:10:09Z","pushed_at":"2017-09-26T15:56:58Z","git_url":"git://github.com/Expensify/Bedrock-PHP.git","ssh_url":"git@github.com:Expensify/Bedrock-PHP.git","clone_url":"https://github.com/Expensify/Bedrock-PHP.git","svn_url":"https://github.com/Expensify/Bedrock-PHP","homepage":null,"size":154,"stargazers_count":3,"watchers_count":3,"language":"PHP","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":1,"forks":1,"open_issues":1,"watchers":3,"default_branch":"master"}},"base":{"label":"Expensify:master","ref":"master","sha":"ff921e249e46478b75c351a4208748e370c7f31d","user":{"login":"Expensify","id":476779,"avatar_url":"https://avatars0.githubusercontent.com/u/476779?v=4","gravatar_id":"","url":"https://api.github.com/users/Expensify","html_url":"https://github.com/Expensify","followers_url":"https://api.github.com/users/Expensify/followers","following_url":"https://api.github.com/users/Expensify/following{/other_user}","gists_url":"https://api.github.com/users/Expensify/gists{/gist_id}","starred_url":"https://api.github.com/users/Expensify/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Expensify/subscriptions","organizations_url":"https://api.github.com/users/Expensify/orgs","repos_url":"https://api.github.com/users/Expensify/repos","events_url":"https://api.github.com/users/Expensify/events{/privacy}","received_events_url":"https://api.github.com/users/Expensify/received_events","type":"Organization","site_admin":false},"repo":{"id":68838259,"name":"Bedrock-PHP","full_name":"Expensify/Bedrock-PHP","owner":{"login":"Expensify","id":476779,"avatar_url":"https://avatars0.githubusercontent.com/u/476779?v=4","gravatar_id":"","url":"https://api.github.com/users/Expensify","html_url":"https://github.com/Expensify","followers_url":"https://api.github.com/users/Expensify/followers","following_url":"https://api.github.com/users/Expensify/following{/other_user}","gists_url":"https://api.github.com/users/Expensify/gists{/gist_id}","starred_url":"https://api.github.com/users/Expensify/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Expensify/subscriptions","organizations_url":"https://api.github.com/users/Expensify/orgs","repos_url":"https://api.github.com/users/Expensify/repos","events_url":"https://api.github.com/users/Expensify/events{/privacy}","received_events_url":"https://api.github.com/users/Expensify/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Expensify/Bedrock-PHP","description":null,"fork":false,"url":"https://api.github.com/repos/Expensify/Bedrock-PHP","forks_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/forks","keys_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/teams","hooks_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/hooks","issue_events_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/issues/events{/number}","events_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/events","assignees_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/assignees{/user}","branches_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/branches{/branch}","tags_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/tags","blobs_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/git/refs{/sha}","trees_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/statuses/{sha}","languages_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/languages","stargazers_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/stargazers","contributors_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/contributors","subscribers_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/subscribers","subscription_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/subscription","commits_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/commits{/sha}","git_commits_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/git/commits{/sha}","comments_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/comments{/number}","issue_comment_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/issues/comments{/number}","contents_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/contents/{+path}","compare_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/merges","archive_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/downloads","issues_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/issues{/number}","pulls_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/pulls{/number}","milestones_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/milestones{/number}","notifications_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/labels{/name}","releases_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/releases{/id}","deployments_url":"https://api.github.com/repos/Expensify/Bedrock-PHP/deployments","created_at":"2016-09-21T16:52:46Z","updated_at":"2017-04-13T00:10:09Z","pushed_at":"2017-09-26T15:56:58Z","git_url":"git://github.com/Expensify/Bedrock-PHP.git","ssh_url":"git@github.com:Expensify/Bedrock-PHP.git","clone_url":"https://github.com/Expensify/Bedrock-PHP.git","svn_url":"https://github.com/Expensify/Bedrock-PHP","homepage":null,"size":154,"stargazers_count":3,"watchers_count":3,"language":"PHP","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":1,"forks":1,"open_issues":1,"watchers":3,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/Expensify/Bedrock-PHP/pulls/41"},"html":{"href":"https://github.com/Expensify/Bedrock-PHP/pull/41"},"issue":{"href":"https://api.github.com/repos/Expensify/Bedrock-PHP/issues/41"},"comments":{"href":"https://api.github.com/repos/Expensify/Bedrock-PHP/issues/41/comments"},"review_comments":{"href":"https://api.github.com/repos/Expensify/Bedrock-PHP/pulls/41/comments"},"review_comment":{"href":"https://api.github.com/repos/Expensify/Bedrock-PHP/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Expensify/Bedrock-PHP/pulls/41/commits"},"statuses":{"href":"https://api.github.com/repos/Expensify/Bedrock-PHP/statuses/6a8fd0ca645fc7978d61c8ccfcb338291ffe668b"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"quinthar"}}