From ea4ec1b0d8cfdc138147cc97940cbccafe2e5014 Mon Sep 17 00:00:00 2001 From: Martin Brecht-Precht Date: Mon, 2 May 2016 22:38:39 +0200 Subject: [PATCH] Initial commit --- README.md | 137 +++++++++++++++- composer.json | 3 +- src/SlackAttachment.php | 225 ++++++++++++++++++++++++++ src/SlackAttachmentField.php | 94 +++++++++++ src/SlackAttachmentFieldInterface.php | 18 +++ src/SlackAttachmentInterface.php | 23 +++ src/SlackClient.php | 199 +++++++++++++++++++++++ src/SlackMessage.php | 198 +++++++++++++++++++++++ src/SlackMessageInterface.php | 43 +++++ test.php | 65 ++++++++ 10 files changed, 1002 insertions(+), 3 deletions(-) create mode 100644 src/SlackAttachment.php create mode 100644 src/SlackAttachmentField.php create mode 100644 src/SlackAttachmentFieldInterface.php create mode 100644 src/SlackAttachmentInterface.php create mode 100644 src/SlackClient.php create mode 100644 src/SlackMessage.php create mode 100644 src/SlackMessageInterface.php create mode 100644 test.php diff --git a/README.md b/README.md index b3dea06..a72fc95 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,135 @@ -# php-slack-client -A PHP library acting as a Slack webhook emitting client. +# PHP Slack Client + +[![Build Status](https://travis-ci.org/markenwerk/php-slack-client.svg?branch=master)](https://travis-ci.org/markenwerk/php-slack-client) +[![Test Coverage](https://codeclimate.com/github/markenwerk/php-slack-client/badges/coverage.svg)](https://codeclimate.com/github/markenwerk/php-slack-client/coverage) +[![Dependency Status](https://www.versioneye.com/user/projects/571f8827fcd19a00415b2836/badge.svg)](https://www.versioneye.com/user/projects/571f8827fcd19a00415b2836) +[![Code Climate](https://codeclimate.com/github/markenwerk/php-slack-client/badges/gpa.svg)](https://codeclimate.com/github/markenwerk/php-slack-client) +[![Latest Stable Version](https://poser.pugx.org/markenwerk/slack-client/v/stable)](https://packagist.org/packages/markenwerk/slack-client) +[![Total Downloads](https://poser.pugx.org/markenwerk/slack-client/downloads)](https://packagist.org/packages/markenwerk/slack-client) +[![License](https://poser.pugx.org/markenwerk/slack-client/license)](https://packagist.org/packages/markenwerk/slack-client) + +A basic Slack client library providing simple posting to Slack channels using the webhook API. + +## Installation + +```{json} +{ + "require": { + "markenwerk/slack-client": "~1.0" + } +} +``` + +## Usage + +### Autoloading and namesapce + +```{php} +require_once('path/to/vendor/autoload.php'); +``` + +### Posting to a channel + +The following example will produce a post in a Slack channel looking like this: + +![Slack ] + +```{php} +use SlackClient\SlackAttachment; +use SlackClient\SlackAttachmentField; +use SlackClient\SlackClient; +use SlackClient\SlackMessage; + +$client = new SlackClient(); +$client + ->setSubdomainName('markenwerk') + ->setToken('') + ->setUsername('PHP SlackClient') + ->setChannel('#log'); + +$message = new SlackMessage(); +$message + ->setText('A basic Slack client library providing simple posting to Slack channels using the webhook API.') + ->setIconUrl('https://avatars2.githubusercontent.com/u/5921253?v=3&s=200') + ->setUnfurlLinks(true) + ->setUnfurlMedia(true); + +$attachment = new SlackAttachment(); +$attachment + ->setText('A basic Slack client library providing simple posting to Slack channels using the webhook API.') + ->setPretext('A basic Slack client library.') + ->setFallback('A basic Slack client library providing simple posting to Slack channels using the webhook API.') + ->setColor(SlackAttachment::COLOR_WARNING); + +$shortAttachmentField = new SlackAttachmentField(); +$shortAttachmentField + ->setTitle('Short field') + ->setValue('Some chars') + ->setShort(true); + +$anotherShortAttachmentField = new SlackAttachmentField(); +$anotherShortAttachmentField + ->setTitle('Short field') + ->setValue('Some chars') + ->setShort(true); + +$attachmentField = new SlackAttachmentField(); +$attachmentField + ->setTitle('Regular field') + ->setValue('Some more chars') + ->setShort(false); + +$anotherAttachmentField = new SlackAttachmentField(); +$anotherAttachmentField + ->setTitle('Regular field') + ->setValue('Some more chars') + ->setShort(false); + +$attachment + ->addField($shortAttachmentField) + ->addField($anotherShortAttachmentField) + ->addField($attachmentField) + ->addField($anotherAttachmentField); + +$message->addAttachment($attachment); + +$client->post($message); +``` + +--- + +## Extending the Basic HTTP Client + +Every part of the client is based upon proper interfaces. Most class instances can get injected into the client itself. +If you want to extend the client just write some classes implementing the according interface and you´re done with that. + +Take a look at the [PHP JSON HTTP Client](https://github.com/markenwerk/php-json-http-client) which is an extension of the PHP Basic HTTP Client. + +--- + +## Exception handling + +PHP Basic HTTP Client provides different exceptions – also provided by the PHP Common Exceptions project – for proper handling. +You can find more information about [PHP Common Exceptions at Github](https://github.com/markenwerk/php-common-exceptions). + +### Exceptions to be expected + +In general you should expect that any setter method could thrown an `\InvalidArgumentException`. The following exceptions could get thrown while using PHP Basic HTTP Client. + +- `CommonException\IoException\FileNotFoundException` on configuring a `ClientCertificateAuthentication`instance +- `CommonException\IoException\FileReadableException` on configuring a `ClientCertificateAuthentication`instance +- `BasicHttpClient\Exception\HttpRequestAuthenticationException` on performing a request +- `BasicHttpClient\Exception\HttpRequestException` on performing a request +- `CommonException\NetworkException\ConnectionTimeoutException` on performing a request +- `CommonException\NetworkException\CurlException` on performing a request + +--- + +## Contribution + +Contributing to our projects is always very appreciated. +**But: please follow the contribution guidelines written down in the [CONTRIBUTING.md](https://github.com/markenwerk/php-slack-client/blob/master/CONTRIBUTING.md) document.** + +## License + +PHP Slack Client is under the MIT license. \ No newline at end of file diff --git a/composer.json b/composer.json index 9ed645e..f0a4c27 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,8 @@ }, "require": { "php": ">=5.3", - "markenwerk/json-http-client": "~2.0" + "markenwerk/basic-http-client": "~2.0", + "markenwerk/common-exceptions": "~2.0" }, "require-dev": { "phpunit/phpunit": "~4.8", diff --git a/src/SlackAttachment.php b/src/SlackAttachment.php new file mode 100644 index 0000000..cdc97ff --- /dev/null +++ b/src/SlackAttachment.php @@ -0,0 +1,225 @@ +color = $color; + return $this; + } + + /** + * @return string + */ + public function getColor() + { + return $this->color; + } + + /** + * @param string $fallback + * @return $this + */ + public function setFallback($fallback) + { + if (!is_string($fallback)) { + $argumentType = (is_object($fallback)) ? get_class($fallback) : gettype($fallback); + throw new \InvalidArgumentException('Expected the fallback text as string. Got ' . $argumentType); + } + $this->fallback = $fallback; + return $this; + } + + /** + * @return string + */ + public function getFallback() + { + return $this->fallback; + } + + /** + * @param SlackAttachmentFieldInterface[] $fields + * @return $this + */ + public function setFields($fields) + { + if (!is_array($fields)) { + $argumentType = (is_object($fields)) ? get_class($fields) : gettype($fields); + throw new \InvalidArgumentException('Expected the attachment fields as array. Got ' . $argumentType); + } + foreach ($fields as $field) { + if (!$field instanceof SlackAttachmentFieldInterface) { + $argumentType = (is_object($field)) ? get_class($field) : gettype($field); + throw new \InvalidArgumentException('Expected the attachment fields as array of SlackAttachmentInterface implementations. Found ' . $argumentType); + } + } + $this->fields = $fields; + return $this; + } + + /** + * @param SlackAttachmentFieldInterface $field + * @return $this + */ + public function addField(SlackAttachmentFieldInterface $field) + { + $this->fields[] = $field; + return $this; + } + + /** + * @param SlackAttachmentFieldInterface $field + * @return $this + */ + public function removeField(SlackAttachmentFieldInterface $field) + { + for ($i = 0; $i < count($this->fields); $i++) { + if ($this->fields[$i] == $field) { + unset($this->fields[$i]); + return $this; + } + } + return $this; + } + + /** + * @return bool + */ + public function hasFields() + { + return count($this->fields) > 0; + } + + /** + * @return int + */ + public function countFields() + { + return count($this->fields); + } + + /** + * @return SlackAttachmentFieldInterface[] + */ + public function getFields() + { + return $this->fields; + } + + /** + * @param string $pretext + * @return $this + */ + public function setPretext($pretext) + { + if (!is_string($pretext)) { + $argumentType = (is_object($pretext)) ? get_class($pretext) : gettype($pretext); + throw new \InvalidArgumentException('Expected the pretext as string. Got ' . $argumentType); + } + $this->pretext = $pretext; + return $this; + } + + /** + * @return string + */ + public function getPretext() + { + return $this->pretext; + } + + /** + * @param string $text + * @return $this + */ + public function setText($text) + { + if (!is_string($text)) { + $argumentType = (is_object($text)) ? get_class($text) : gettype($text); + throw new \InvalidArgumentException('Expected the text as string. Got ' . $argumentType); + } + $this->text = $text; + return $this; + } + + /** + * @return string + */ + public function getText() + { + return $this->text; + } + + /** + * @return array + */ + public function toArray() + { + $attachment = array( + 'fallback' => $this->getFallback(), + 'color' => $this->getColor(), + ); + if (!is_null($this->getText())) { + $attachment['text'] = $this->getText(); + } + if (!is_null($this->getPretext())) { + $attachment['pretext'] = $this->getPretext(); + } + foreach ($this->getFields() as $field) { + if (!isset($attachment['fields'])) { + $attachment['fields'] = array(); + } + $attachment['fields'][] = $field->toArray(); + } + return $attachment; + } + +} diff --git a/src/SlackAttachmentField.php b/src/SlackAttachmentField.php new file mode 100644 index 0000000..8e98768 --- /dev/null +++ b/src/SlackAttachmentField.php @@ -0,0 +1,94 @@ +short = $short; + return $this; + } + + /** + * @return bool + */ + public function getShort() + { + return $this->short; + } + + /** + * @param string $title + * @return $this + */ + public function setTitle($title) + { + $this->title = $title; + return $this; + } + + /** + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * @param string $value + * @return $this + */ + public function setValue($value) + { + $this->value = $value; + return $this; + } + + /** + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * @return array + */ + public function toArray() + { + return array( + 'title' => $this->getTitle(), + 'value' => $this->getValue(), + 'short' => $this->getShort(), + ); + } + +} diff --git a/src/SlackAttachmentFieldInterface.php b/src/SlackAttachmentFieldInterface.php new file mode 100644 index 0000000..a37cfd6 --- /dev/null +++ b/src/SlackAttachmentFieldInterface.php @@ -0,0 +1,18 @@ +subdomainName; + } + + /** + * @param string $subdomainName + * @return $this + */ + public function setSubdomainName($subdomainName) + { + if (!is_string($subdomainName)) { + $argumentType = (is_object($subdomainName)) ? get_class($subdomainName) : gettype($subdomainName); + throw new \InvalidArgumentException('Expected the subdomain name as string. Got ' . $argumentType); + } + $this->subdomainName = $subdomainName; + return $this; + } + + /** + * @return string + */ + public function getToken() + { + return $this->token; + } + + /** + * @param string $token + * @return $this + */ + public function setToken($token) + { + if (!is_string($token)) { + $argumentType = (is_object($token)) ? get_class($token) : gettype($token); + throw new \InvalidArgumentException('Expected the token as string. Got ' . $argumentType); + } + $this->token = $token; + return $this; + } + + /** + * @return string + */ + public function getChannel() + { + return $this->channel; + } + + /** + * @param string $channel + * @return $this + */ + public function setChannel($channel) + { + if (!is_string($channel)) { + $argumentType = (is_object($channel)) ? get_class($channel) : gettype($channel); + throw new \InvalidArgumentException('Expected the channel name as string. Got ' . $argumentType); + } + if (mb_substr($channel, 0, 1) != '#') { + throw new \InvalidArgumentException('The channel name is invalid. It has to start with "#".'); + } + $this->channel = $channel; + return $this; + } + + /** + * @return string + */ + public function getUsername() + { + return $this->username; + } + + /** + * @param string $username + * @return $this + */ + public function setUsername($username) + { + if (!is_string($username)) { + $argumentType = (is_object($username)) ? get_class($username) : gettype($username); + throw new \InvalidArgumentException('Expected the username as string. Got ' . $argumentType); + } + $this->username = $username; + return $this; + } + + /** + * @param SlackMessageInterface $slackMessage + * @return $this + */ + public function post(SlackMessageInterface $slackMessage) + { + $payload = array( + 'channel' => $this->getChannel(), + 'username' => $this->getUsername(), + 'text' => $slackMessage->getText(), + 'unfurl_links' => $slackMessage->getUnfurlLinks(), + 'unfurl_media' => $slackMessage->getUnfurlMedia(), + ); + if (!is_null($slackMessage->getIconUrl())) { + $payload['icon_url'] = $slackMessage->getIconUrl(); + } + if ($slackMessage->hasAttachments()) { + foreach ($slackMessage->getAttachments() as $attachment) { + if (!isset($payload['attachments'])) { + $payload['attachments'] = array(); + } + $payload['attachments'][] = $attachment->toArray(); + } + } + $endpoint = 'https://' . $this->getSubdomainName() . '.slack.com/services/hooks/incoming-webhook?token=' + . $this->getToken(); + $this->performRequest($endpoint, $payload); + return $this; + } + + /** + * @param $endpoint + * @param array $payload + * @throws StringifyException + * @throws UnexpectedResponseException + * @throws ConnectionTimeoutException + * @throws CurlException + */ + protected function performRequest($endpoint, array $payload) + { + $requestBody = @json_encode($payload); + if ($requestBody === false) { + throw new StringifyException('Building payload failed.'); + } + $message = new Message(); + $message + ->addHeader(new Header('Content-Type', array('application/json'))) + ->addHeader(new Header('Accept', array('application/json'))) + ->setBody(new Body($requestBody)); + $request = new Request(); + $response = $request + ->setTransport(new HttpsTransport()) + ->setUrl(new Url($endpoint)) + ->setMethod(Request::REQUEST_METHOD_POST) + ->setMessage($message) + ->perform() + ->getResponse(); + if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) { + throw new UnexpectedResponseException( + 'Slack API responded unexpected with HTTP status ' . (string)$response->getStatusText() + ); + } + } + +} diff --git a/src/SlackMessage.php b/src/SlackMessage.php new file mode 100644 index 0000000..7ee8eee --- /dev/null +++ b/src/SlackMessage.php @@ -0,0 +1,198 @@ +text; + } + + /** + * @param string $text + * @return $this + */ + public function setText($text) + { + if (!is_string($text)) { + $argumentType = (is_object($text)) ? get_class($text) : gettype($text); + throw new \InvalidArgumentException('Expected the text as string. Got ' . $argumentType); + } + $this->text = $text; + return $this; + } + + /** + * @return SlackAttachmentInterface[] + */ + public function getAttachments() + { + return $this->attachments; + } + + /** + * @param SlackAttachmentInterface[] $attachments + * @return $this + */ + public function setAttachments($attachments) + { + if (!is_array($attachments)) { + $argumentType = (is_object($attachments)) ? get_class($attachments) : gettype($attachments); + throw new \InvalidArgumentException('Expected the attachments as array. Got ' . $argumentType); + } + foreach ($attachments as $attachment) { + if (!$attachment instanceof SlackAttachmentInterface) { + $argumentType = (is_object($attachment)) ? get_class($attachment) : gettype($attachment); + throw new \InvalidArgumentException('Expected the attachments as array of SlackAttachmentInterface implementations. Found ' . $argumentType); + } + } + $this->attachments = $attachments; + return $this; + } + + /** + * @param SlackAttachmentInterface $attachment + * @return $this + */ + public function addAttachment(SlackAttachmentInterface $attachment) + { + $this->attachments[] = $attachment; + return $this; + } + + /** + * @param SlackAttachment $attachment + * @return $this + */ + public function removeAttachment(SlackAttachment $attachment) + { + for ($i = 0; $i < count($this->attachments); $i++) { + if ($this->attachments[$i] == $attachment) { + unset($this->attachments[$i]); + return $this; + } + } + return $this; + } + + /** + * @return bool + */ + public function hasAttachments() + { + return count($this->attachments) > 0; + } + + /** + * @return int + */ + public function countAttachments() + { + return count($this->attachments); + } + + /** + * @return string + */ + public function getIconUrl() + { + return $this->iconUrl; + } + + /** + * @param string $iconUrl + * @return $this + */ + public function setIconUrl($iconUrl) + { + if (!is_string($iconUrl)) { + $argumentType = (is_object($iconUrl)) ? get_class($iconUrl) : gettype($iconUrl); + throw new \InvalidArgumentException('Expected the icon URL as string. Got ' . $argumentType); + } + if (filter_var($iconUrl, FILTER_VALIDATE_URL) === false) { + throw new \InvalidArgumentException('The icon URL is invalid.'); + } + $this->iconUrl = $iconUrl; + return $this; + } + + /** + * @return bool + */ + public function getUnfurlLinks() + { + return $this->unfurlLinks; + } + + /** + * @param bool $unfurlLinks + * @return $this + */ + public function setUnfurlLinks($unfurlLinks) + { + if (!is_bool($unfurlLinks)) { + $argumentType = (is_object($unfurlLinks)) ? get_class($unfurlLinks) : gettype($unfurlLinks); + throw new \InvalidArgumentException('Expected the unfurl links setting as boolean. Got ' . $argumentType); + } + $this->unfurlLinks = $unfurlLinks; + return $this; + } + + /** + * @return bool + */ + public function getUnfurlMedia() + { + return $this->unfurlMedia; + } + + /** + * @param bool $unfurlMedia + * @return $this + */ + public function setUnfurlMedia($unfurlMedia) + { + if (!is_bool($unfurlMedia)) { + $argumentType = (is_object($unfurlMedia)) ? get_class($unfurlMedia) : gettype($unfurlMedia); + throw new \InvalidArgumentException('Expected the unfurl media setting as boolean. Got ' . $argumentType); + } + $this->unfurlMedia = $unfurlMedia; + return $this; + } + +} diff --git a/src/SlackMessageInterface.php b/src/SlackMessageInterface.php new file mode 100644 index 0000000..90e6293 --- /dev/null +++ b/src/SlackMessageInterface.php @@ -0,0 +1,43 @@ +setSubdomainName('markenwerk') + ->setToken('') + ->setUsername('PHP SlackClient') + ->setChannel('#log'); + +$message = new SlackMessage(); +$message + ->setText('A basic Slack client library providing simple posting to Slack channels using the webhook API.') + ->setIconUrl('https://avatars2.githubusercontent.com/u/5921253?v=3&s=200') + ->setUnfurlLinks(true) + ->setUnfurlMedia(true); + +$attachment = new SlackAttachment(); +$attachment + ->setText('A basic Slack client library providing simple posting to Slack channels using the webhook API.') + ->setPretext('A basic Slack client library.') + ->setFallback('A basic Slack client library providing simple posting to Slack channels using the webhook API.') + ->setColor(SlackAttachment::COLOR_WARNING); + +$shortAttachmentField = new SlackAttachmentField(); +$shortAttachmentField + ->setTitle('Short field') + ->setValue('Some chars') + ->setShort(true); + +$anotherShortAttachmentField = new SlackAttachmentField(); +$anotherShortAttachmentField + ->setTitle('Short field') + ->setValue('Some chars') + ->setShort(true); + +$attachmentField = new SlackAttachmentField(); +$attachmentField + ->setTitle('Regular field') + ->setValue('Some more chars') + ->setShort(false); + +$anotherAttachmentField = new SlackAttachmentField(); +$anotherAttachmentField + ->setTitle('Regular field') + ->setValue('Some more chars') + ->setShort(false); + +$attachment + ->addField($shortAttachmentField) + ->addField($anotherShortAttachmentField) + ->addField($attachmentField) + ->addField($anotherAttachmentField); + +$message->addAttachment($attachment); + +$client->post($message);