{"action":"opened","number":519,"pull_request":{"url":"https://api.github.com/repos/Seldaek/monolog/pulls/519","id":30257228,"html_url":"https://github.com/Seldaek/monolog/pull/519","diff_url":"https://github.com/Seldaek/monolog/pull/519.diff","patch_url":"https://github.com/Seldaek/monolog/pull/519.patch","issue_url":"https://api.github.com/repos/Seldaek/monolog/issues/519","number":519,"state":"open","locked":false,"title":"Make Swift_Message more easily customizable and lazy when sending email","user":{"login":"johnkary","id":135607,"avatar_url":"https://avatars.githubusercontent.com/u/135607?v=3","gravatar_id":"","url":"https://api.github.com/users/johnkary","html_url":"https://github.com/johnkary","followers_url":"https://api.github.com/users/johnkary/followers","following_url":"https://api.github.com/users/johnkary/following{/other_user}","gists_url":"https://api.github.com/users/johnkary/gists{/gist_id}","starred_url":"https://api.github.com/users/johnkary/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/johnkary/subscriptions","organizations_url":"https://api.github.com/users/johnkary/orgs","repos_url":"https://api.github.com/users/johnkary/repos","events_url":"https://api.github.com/users/johnkary/events{/privacy}","received_events_url":"https://api.github.com/users/johnkary/received_events","type":"User","site_admin":false},"body":"This is an alternate implementation of #497 that not only allows customization of Swift_Message based on logged data, but also adds a performance gain when using the Swift_Message callback option. There is one possible BC break for anyone extending SwiftMailerHandler. See the bottom of this PR.\r\n\r\n### Added Swift_Mailer as a dev dependency\r\n\r\nNew test cases exercise SwiftMailerHandler using real instances of Swift_Message.\r\n\r\n### SwiftMailerHandler subclasses can customize Swift_Message given logged data\r\n\r\nPrior to this PR a `SwiftMailerHandler` subclass had to override the entire `send()` method to customize the sent Swift_Message. After this PR subclasses can instead override the `buildMessage()` function. This was originally proposed in #497 and the implementation here is mostly the same.\r\n\r\nCustomizing the sent Swift_Message in a subclass:\r\n```php\r\nclass CustomSwiftMailerHandler extends SwiftMailerHandler\r\n{\r\n    protected function buildMessage($content, array $records)\r\n    {\r\n        $subject = 'Error Occurred';\r\n        if (count($records) > 1) {\r\n            $subject = 'Many Errors Occurred';\r\n        }\r\n\r\n        $message = new \\Swift_Message;\r\n        $message->setSubject($subject);\r\n\r\n        return $message;\r\n    }\r\n}\r\n```\r\n\r\n### SwiftMailerHandler Swift_Message callback now supports customization given logged data\r\n\r\nThe constructor callable is now passed two new arguments: the logged contents and logged records. Either could be used to further customized the sent Swift_Message.\r\n\r\n```php\r\n$callback = function ($content, array $records) {\r\n    $subject = 'Error Occurred';\r\n    if (count($records) > 1) {\r\n        $subject = 'Many Errors Occurred';\r\n    }\r\n\r\n    $message = new \\Swift_Message;\r\n    $message->setSubject($subject);\r\n\r\n    return $message;\r\n};\r\n\r\n$handler = new SwiftMailerHandler($mailer, $callback);\r\n```\r\n\r\nUsing a callback to customize Swift_Message has been supported by the Symfony MonologBundle for some time: https://github.com/symfony/MonologBundle/blob/master/DependencyInjection/MonologExtension.php#L389\r\n\r\n```yaml\r\nmonolog:\r\n    handlers:\r\n        # ... snipped some other config here ...\r\n        swift:\r\n            type:       swift_mailer\r\n            level:      error\r\n            # Below service builds the Swift_Message that will be sent\r\n            email_prototype:\r\n                id: my_error_email_prototype\r\n                method: buildMessage\r\n```\r\n\r\n### Swift_Message callback is now only invoked when mail will be sent\r\n\r\nFor most applications, sending email as part of any request is a rare event. In addition, SwiftMailerHandler currently only sends email when a message is logged with ERROR severity. Handling an ERROR message in most applications should be even more rare than sending normal lifecycle emails.\r\n\r\nLaziness is desired for performance reasons. Prior to this PR, a Swift_Message object would be instantiated even if no email was sent because SwiftMailerHandler constructor required an injected Swift_Message OR the callback to create a Swift_Message was always invoked. After this PR the callback is only invoked if an email is going to be sent. (Any code injecting a Swift_Message object will of course still incur the expense to instantiate it.)\r\n\r\nIn fact Symfony MonologBundle specifically [makes Swift_Message creation lazy](https://github.com/symfony/MonologBundle/pull/109) when not specifying a callback function. (The linked PR alludes to how expensive it is to instantiate Swift_Message.) This PR adds lazy support to MonologBundle when a callback is configured.\r\n\r\n### Potential Backwards Compatibility break for subclasses using SwiftMailerHandler `$this->message`\r\n\r\nBefore this PR a SwiftMailerHandler subclass could always assume `$this->message` was an instance of Swift_Message because the constructor stores the injected Swift_Message or resolves the callable to create a Swift_Message.\r\n\r\nIf you always inject a Swift_Message object, no change is necessary. But if your subclass allows a developer to inject a Swift_Message OR a callback, you'll need to call `$this->buildMessage()` instead of `$this->message`\r\n\r\nI would be open to fully removing `protected $message;` and replacing it with `private $template;` and `protected function buildMessage()`. This way the callable vs injected Swift_Message is private implementation details, and anyone relying on `$this->message` being present would see a more obvious error message that it no longer exists.","created_at":"2015-03-02T03:55:40Z","updated_at":"2015-03-02T03:55:40Z","closed_at":null,"merged_at":null,"merge_commit_sha":"5b63c00939767aa19b3dc908647a2600d1ed68db","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/Seldaek/monolog/pulls/519/commits","review_comments_url":"https://api.github.com/repos/Seldaek/monolog/pulls/519/comments","review_comment_url":"https://api.github.com/repos/Seldaek/monolog/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Seldaek/monolog/issues/519/comments","statuses_url":"https://api.github.com/repos/Seldaek/monolog/statuses/858ee29fb095a8e7c53979325fca53d02e0c861f","head":{"label":"johnkary:dynamicSwiftMessage","ref":"dynamicSwiftMessage","sha":"858ee29fb095a8e7c53979325fca53d02e0c861f","user":{"login":"johnkary","id":135607,"avatar_url":"https://avatars.githubusercontent.com/u/135607?v=3","gravatar_id":"","url":"https://api.github.com/users/johnkary","html_url":"https://github.com/johnkary","followers_url":"https://api.github.com/users/johnkary/followers","following_url":"https://api.github.com/users/johnkary/following{/other_user}","gists_url":"https://api.github.com/users/johnkary/gists{/gist_id}","starred_url":"https://api.github.com/users/johnkary/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/johnkary/subscriptions","organizations_url":"https://api.github.com/users/johnkary/orgs","repos_url":"https://api.github.com/users/johnkary/repos","events_url":"https://api.github.com/users/johnkary/events{/privacy}","received_events_url":"https://api.github.com/users/johnkary/received_events","type":"User","site_admin":false},"repo":{"id":29512989,"name":"monolog","full_name":"johnkary/monolog","owner":{"login":"johnkary","id":135607,"avatar_url":"https://avatars.githubusercontent.com/u/135607?v=3","gravatar_id":"","url":"https://api.github.com/users/johnkary","html_url":"https://github.com/johnkary","followers_url":"https://api.github.com/users/johnkary/followers","following_url":"https://api.github.com/users/johnkary/following{/other_user}","gists_url":"https://api.github.com/users/johnkary/gists{/gist_id}","starred_url":"https://api.github.com/users/johnkary/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/johnkary/subscriptions","organizations_url":"https://api.github.com/users/johnkary/orgs","repos_url":"https://api.github.com/users/johnkary/repos","events_url":"https://api.github.com/users/johnkary/events{/privacy}","received_events_url":"https://api.github.com/users/johnkary/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/johnkary/monolog","description":"Sends your logs to files, sockets, inboxes, databases and various web services","fork":true,"url":"https://api.github.com/repos/johnkary/monolog","forks_url":"https://api.github.com/repos/johnkary/monolog/forks","keys_url":"https://api.github.com/repos/johnkary/monolog/keys{/key_id}","collaborators_url":"https://api.github.com/repos/johnkary/monolog/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/johnkary/monolog/teams","hooks_url":"https://api.github.com/repos/johnkary/monolog/hooks","issue_events_url":"https://api.github.com/repos/johnkary/monolog/issues/events{/number}","events_url":"https://api.github.com/repos/johnkary/monolog/events","assignees_url":"https://api.github.com/repos/johnkary/monolog/assignees{/user}","branches_url":"https://api.github.com/repos/johnkary/monolog/branches{/branch}","tags_url":"https://api.github.com/repos/johnkary/monolog/tags","blobs_url":"https://api.github.com/repos/johnkary/monolog/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/johnkary/monolog/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/johnkary/monolog/git/refs{/sha}","trees_url":"https://api.github.com/repos/johnkary/monolog/git/trees{/sha}","statuses_url":"https://api.github.com/repos/johnkary/monolog/statuses/{sha}","languages_url":"https://api.github.com/repos/johnkary/monolog/languages","stargazers_url":"https://api.github.com/repos/johnkary/monolog/stargazers","contributors_url":"https://api.github.com/repos/johnkary/monolog/contributors","subscribers_url":"https://api.github.com/repos/johnkary/monolog/subscribers","subscription_url":"https://api.github.com/repos/johnkary/monolog/subscription","commits_url":"https://api.github.com/repos/johnkary/monolog/commits{/sha}","git_commits_url":"https://api.github.com/repos/johnkary/monolog/git/commits{/sha}","comments_url":"https://api.github.com/repos/johnkary/monolog/comments{/number}","issue_comment_url":"https://api.github.com/repos/johnkary/monolog/issues/comments{/number}","contents_url":"https://api.github.com/repos/johnkary/monolog/contents/{+path}","compare_url":"https://api.github.com/repos/johnkary/monolog/compare/{base}...{head}","merges_url":"https://api.github.com/repos/johnkary/monolog/merges","archive_url":"https://api.github.com/repos/johnkary/monolog/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/johnkary/monolog/downloads","issues_url":"https://api.github.com/repos/johnkary/monolog/issues{/number}","pulls_url":"https://api.github.com/repos/johnkary/monolog/pulls{/number}","milestones_url":"https://api.github.com/repos/johnkary/monolog/milestones{/number}","notifications_url":"https://api.github.com/repos/johnkary/monolog/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/johnkary/monolog/labels{/name}","releases_url":"https://api.github.com/repos/johnkary/monolog/releases{/id}","created_at":"2015-01-20T05:11:05Z","updated_at":"2015-01-20T05:11:06Z","pushed_at":"2015-03-02T02:05:09Z","git_url":"git://github.com/johnkary/monolog.git","ssh_url":"git@github.com:johnkary/monolog.git","clone_url":"https://github.com/johnkary/monolog.git","svn_url":"https://github.com/johnkary/monolog","homepage":"","size":1463,"stargazers_count":0,"watchers_count":0,"language":"PHP","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"Seldaek:master","ref":"master","sha":"1b34cd4d88b2c10e44cc0aa3001e923b737bc5de","user":{"login":"Seldaek","id":183678,"avatar_url":"https://avatars.githubusercontent.com/u/183678?v=3","gravatar_id":"","url":"https://api.github.com/users/Seldaek","html_url":"https://github.com/Seldaek","followers_url":"https://api.github.com/users/Seldaek/followers","following_url":"https://api.github.com/users/Seldaek/following{/other_user}","gists_url":"https://api.github.com/users/Seldaek/gists{/gist_id}","starred_url":"https://api.github.com/users/Seldaek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Seldaek/subscriptions","organizations_url":"https://api.github.com/users/Seldaek/orgs","repos_url":"https://api.github.com/users/Seldaek/repos","events_url":"https://api.github.com/users/Seldaek/events{/privacy}","received_events_url":"https://api.github.com/users/Seldaek/received_events","type":"User","site_admin":false},"repo":{"id":1376664,"name":"monolog","full_name":"Seldaek/monolog","owner":{"login":"Seldaek","id":183678,"avatar_url":"https://avatars.githubusercontent.com/u/183678?v=3","gravatar_id":"","url":"https://api.github.com/users/Seldaek","html_url":"https://github.com/Seldaek","followers_url":"https://api.github.com/users/Seldaek/followers","following_url":"https://api.github.com/users/Seldaek/following{/other_user}","gists_url":"https://api.github.com/users/Seldaek/gists{/gist_id}","starred_url":"https://api.github.com/users/Seldaek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Seldaek/subscriptions","organizations_url":"https://api.github.com/users/Seldaek/orgs","repos_url":"https://api.github.com/users/Seldaek/repos","events_url":"https://api.github.com/users/Seldaek/events{/privacy}","received_events_url":"https://api.github.com/users/Seldaek/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/Seldaek/monolog","description":"Sends your logs to files, sockets, inboxes, databases and various web services","fork":false,"url":"https://api.github.com/repos/Seldaek/monolog","forks_url":"https://api.github.com/repos/Seldaek/monolog/forks","keys_url":"https://api.github.com/repos/Seldaek/monolog/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Seldaek/monolog/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Seldaek/monolog/teams","hooks_url":"https://api.github.com/repos/Seldaek/monolog/hooks","issue_events_url":"https://api.github.com/repos/Seldaek/monolog/issues/events{/number}","events_url":"https://api.github.com/repos/Seldaek/monolog/events","assignees_url":"https://api.github.com/repos/Seldaek/monolog/assignees{/user}","branches_url":"https://api.github.com/repos/Seldaek/monolog/branches{/branch}","tags_url":"https://api.github.com/repos/Seldaek/monolog/tags","blobs_url":"https://api.github.com/repos/Seldaek/monolog/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Seldaek/monolog/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Seldaek/monolog/git/refs{/sha}","trees_url":"https://api.github.com/repos/Seldaek/monolog/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Seldaek/monolog/statuses/{sha}","languages_url":"https://api.github.com/repos/Seldaek/monolog/languages","stargazers_url":"https://api.github.com/repos/Seldaek/monolog/stargazers","contributors_url":"https://api.github.com/repos/Seldaek/monolog/contributors","subscribers_url":"https://api.github.com/repos/Seldaek/monolog/subscribers","subscription_url":"https://api.github.com/repos/Seldaek/monolog/subscription","commits_url":"https://api.github.com/repos/Seldaek/monolog/commits{/sha}","git_commits_url":"https://api.github.com/repos/Seldaek/monolog/git/commits{/sha}","comments_url":"https://api.github.com/repos/Seldaek/monolog/comments{/number}","issue_comment_url":"https://api.github.com/repos/Seldaek/monolog/issues/comments{/number}","contents_url":"https://api.github.com/repos/Seldaek/monolog/contents/{+path}","compare_url":"https://api.github.com/repos/Seldaek/monolog/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Seldaek/monolog/merges","archive_url":"https://api.github.com/repos/Seldaek/monolog/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Seldaek/monolog/downloads","issues_url":"https://api.github.com/repos/Seldaek/monolog/issues{/number}","pulls_url":"https://api.github.com/repos/Seldaek/monolog/pulls{/number}","milestones_url":"https://api.github.com/repos/Seldaek/monolog/milestones{/number}","notifications_url":"https://api.github.com/repos/Seldaek/monolog/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Seldaek/monolog/labels{/name}","releases_url":"https://api.github.com/repos/Seldaek/monolog/releases{/id}","created_at":"2011-02-17T02:07:15Z","updated_at":"2015-03-01T17:03:04Z","pushed_at":"2015-03-01T15:09:30Z","git_url":"git://github.com/Seldaek/monolog.git","ssh_url":"git@github.com:Seldaek/monolog.git","clone_url":"https://github.com/Seldaek/monolog.git","svn_url":"https://github.com/Seldaek/monolog","homepage":"","size":4301,"stargazers_count":3099,"watchers_count":3099,"language":"PHP","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":548,"mirror_url":null,"open_issues_count":33,"forks":548,"open_issues":33,"watchers":3099,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/Seldaek/monolog/pulls/519"},"html":{"href":"https://github.com/Seldaek/monolog/pull/519"},"issue":{"href":"https://api.github.com/repos/Seldaek/monolog/issues/519"},"comments":{"href":"https://api.github.com/repos/Seldaek/monolog/issues/519/comments"},"review_comments":{"href":"https://api.github.com/repos/Seldaek/monolog/pulls/519/comments"},"review_comment":{"href":"https://api.github.com/repos/Seldaek/monolog/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Seldaek/monolog/pulls/519/commits"},"statuses":{"href":"https://api.github.com/repos/Seldaek/monolog/statuses/858ee29fb095a8e7c53979325fca53d02e0c861f"}},"merged":false,"mergeable":true,"mergeable_state":"clean","merged_by":null,"comments":0,"review_comments":0,"commits":2,"additions":93,"deletions":10,"changed_files":3}}