├── .gitignore ├── .php_cs ├── .styleci.yml ├── .travis.yml ├── LICENSE ├── README.md ├── appveyor.yml ├── composer.json ├── docs ├── Makefile ├── book │ ├── getting_started.rst │ ├── usage.rst │ └── usage │ │ ├── repository.rst │ │ └── sender.rst ├── conf.py └── index.rst ├── phpunit.xml.dist ├── src ├── Driver │ ├── AbstractDriver.php │ ├── Doctrine │ │ ├── AbstractDoctrineDriver.php │ │ └── ORM │ │ │ ├── DoctrineORMDriver.php │ │ │ └── Entity │ │ │ ├── Conversation.php │ │ │ ├── ConversationPerson.php │ │ │ ├── Message.php │ │ │ ├── MessagePerson.php │ │ │ └── Tag.php │ └── DriverInterface.php ├── Event │ ├── ConversationEvent.php │ ├── MessageEvent.php │ └── SymfonyEvent.php ├── EventDispatcher │ ├── EventDispatcherInterface.php │ ├── NativeEventDispatcher.php │ └── SymfonyBridgeEventDispatcher.php ├── Model │ ├── Conversation.php │ ├── ConversationInterface.php │ ├── ConversationPerson.php │ ├── ConversationPersonInterface.php │ ├── Message.php │ ├── MessageInterface.php │ ├── MessagePerson.php │ ├── MessagePersonInterface.php │ ├── PersonInterface.php │ └── TagInterface.php ├── Repository.php ├── RepositoryInterface.php ├── Sender.php ├── SenderInterface.php ├── Tagger.php └── TaggerInterface.php └── tests ├── Driver ├── AbstractDriverTest.php └── Doctrine │ └── ORM │ ├── DoctrineORMDriverTest.php │ └── Entity │ └── TestPerson.php ├── EventDispatcher └── NativeEventDispatcherTest.php ├── RepositoryTest.php ├── SenderTest.php └── TaggerTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor/ 3 | .php_cs.cache 4 | docs/_build 5 | -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | 12 | 13 | For the full copyright and license information, please view the LICENSE 14 | file that was distributed with this source code. 15 | EOF; 16 | 17 | HeaderCommentFixer::setHeader($header); 18 | 19 | return ConfigBridge::create() 20 | ->setUsingCache(true) 21 | ; 22 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: recommended 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | sudo: false 4 | 5 | cache: 6 | directories: 7 | - $HOME/.composer/cache/files 8 | 9 | matrix: 10 | include: 11 | - php: 5.4 12 | - php: 5.5 13 | - php: 5.6 14 | - php: 7.0 15 | - php: 7.0 16 | env: COMPOSER_FLAGS='--prefer-lowest --prefer-stable' 17 | - php: hhvm 18 | 19 | before_install: 20 | - if [[ $TRAVIS_PHP_VERSION != hhvm ]]; then phpenv config-rm xdebug.ini; fi; 21 | - composer self-update 22 | 23 | install: composer update $COMPOSER_FLAGS --prefer-dist --no-interaction 24 | 25 | script: vendor/bin/phpunit 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2011 FriendsOfSymfony 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FOSMessage 2 | ========== 3 | 4 | [![Build Status](https://travis-ci.org/FriendsOfSymfony/FOSMessage.svg?branch=master)](https://travis-ci.org/FriendsOfSymfony/FOSMessage) 5 | [![Build status](https://ci.appveyor.com/api/projects/status/5h1rnsmk8hg4rkbf?svg=true)](https://ci.appveyor.com/project/tgalopin/fosmessage) 6 | 7 | FOSMessage is a PHP 5.4+ framework-agnostic library providing a data structure 8 | and common features to set up user-to-user messaging systems. 9 | 10 | You can think of it as a model for your messaging features : it will take care of the consistency 11 | of the data for you in order to easily create a full-featured messaging system. 12 | 13 | > *Note* : This library is currently in development. You can test it in your project 14 | > (the Composer installation process is very simple), but you should not use it in production 15 | > for the moment. 16 | 17 | This library is based on concepts shared by most modern frameworks (dependency injection, 18 | event dispatching, abstract data drivers, etc.) and therefore, it’s very easy to set it up in any 19 | kind of context. 20 | 21 | If you want to set it up in Symfony, *FOSMesageBundle* is being developed in a new version 22 | (not ready yet). 23 | 24 | Documentation 25 | ------------- 26 | 27 | You can [read the documentation here](http://fosmessage.readthedocs.org). 28 | 29 | Usage example 30 | ------------- 31 | 32 | An implementation example is available on Github: 33 | [tgalopin/FOSMessage-demo](https://github.com/tgalopin/FOSMessage-demo). 34 | 35 | Key features 36 | ------------ 37 | 38 | - Conversation-based messaging 39 | - Multiple conversations participants support 40 | - Very easy to implement (at least in most of the cases) 41 | - Framework-agnotic 42 | - Doctrine ORM and Mongo ODM support 43 | - Not linked to user system implementation 44 | - Optionnal tagging system to organize conversations 45 | - Event system to let developer execute actions on key steps 46 | - Implemented in framework-specific bundle / module 47 | - PHP7 and HHVM support 48 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | build: false 2 | platform: x86 3 | clone_folder: c:\projects\fos-message 4 | 5 | cache: 6 | - c:\php -> appveyor.yml 7 | 8 | init: 9 | - SET PATH=c:\php;%PATH% 10 | - SET COMPOSER_NO_INTERACTION=1 11 | - SET PHP=1 12 | 13 | environment: 14 | matrix: 15 | - COMPOSER_FLAGS: "" 16 | - COMPOSER_FLAGS: --prefer-lowest --prefer-stable 17 | 18 | install: 19 | - IF EXIST c:\php (SET PHP=0) ELSE (mkdir c:\php) 20 | - cd c:\php 21 | - IF %PHP%==1 appveyor DownloadFile http://windows.php.net/downloads/releases/archives/php-5.5.9-nts-Win32-VC11-x86.zip 22 | - IF %PHP%==1 7z x php-5.5.9-nts-Win32-VC11-x86.zip -y >nul 23 | - IF %PHP%==1 del /Q *.zip 24 | - IF %PHP%==1 echo @php %%~dp0composer.phar %%* > composer.bat 25 | - IF %PHP%==1 copy /Y php.ini-development php.ini 26 | - IF %PHP%==1 echo max_execution_time=1200 >> php.ini 27 | - IF %PHP%==1 echo date.timezone="UTC" >> php.ini 28 | - IF %PHP%==1 echo extension_dir=ext >> php.ini 29 | - IF %PHP%==1 echo extension=php_openssl.dll >> php.ini 30 | - IF %PHP%==1 echo extension=php_mbstring.dll >> php.ini 31 | - IF %PHP%==1 echo extension=php_pdo_sqlite.dll >> php.ini 32 | - IF %PHP%==1 echo memory_limit=1G >> php.ini 33 | - appveyor DownloadFile https://getcomposer.org/composer.phar 34 | - cd c:\projects\fos-message 35 | - mkdir %APPDATA%\Composer 36 | - composer update %COMPOSER_FLAGS% --prefer-dist --no-progress --ansi 37 | 38 | test_script: 39 | - cd c:\projects\fos-message 40 | - vendor\bin\phpunit.bat --verbose 41 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "friendsofsymfony/message", 3 | "type": "library", 4 | "description": "A framework-agnostic PHP 5.4+ user-to-user messaging library", 5 | "keywords": [ 6 | "messaging", 7 | "message", 8 | "notification", 9 | "user-to-user" 10 | ], 11 | "homepage": "https://github.com/FriendsOfSymfony/FOSMessage", 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "name": "Titouan Galopin", 16 | "email": "galopintitouan@gmail.com", 17 | "homepage": "http://titouangalopin.com" 18 | } 19 | ], 20 | "require": { 21 | "php": ">=5.4", 22 | "doctrine/collections": "^1.3.0", 23 | "webmozart/assert": "^1.0" 24 | }, 25 | "require-dev": { 26 | "phpunit/phpunit": "^4.8.9", 27 | "doctrine/orm": "^2.5.1", 28 | "sllh/php-cs-fixer-styleci-bridge": "~1.3", 29 | "mockery/mockery": "^0.9.4" 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "FOS\\Message\\": "src/" 34 | } 35 | }, 36 | "autoload-dev": { 37 | "psr-4": { 38 | "FOS\\Message\\Tests\\": "tests/" 39 | } 40 | }, 41 | "extra": { 42 | "branch-alias": { 43 | "dev-master": "1.0.x-dev" 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/FOSMessage.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/FOSMessage.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/FOSMessage" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/FOSMessage" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /docs/book/getting_started.rst: -------------------------------------------------------------------------------- 1 | Getting started 2 | =============== 3 | 4 | Requirements 5 | ------------ 6 | 7 | FOSMessage only supports Doctrine ORM for the moment but it will support 8 | Doctrine ODM in the future. Therefore, for now, you need Doctrine ORM: 9 | 10 | .. code-block:: bash 11 | 12 | composer require doctrine/orm 13 | 14 | 15 | Installation 16 | ------------ 17 | 18 | This bundle is available on Packagist. You can install it using Composer: 19 | 20 | .. code-block:: bash 21 | 22 | composer require friendsofsymfony/message:1.0.x-dev 23 | 24 | .. important:: 25 | 26 | You should **not** use development versions in Composer: we are using it here 27 | only because the library is currently in development. When the library will be 28 | released, change that version to follow semantic versionning. 29 | 30 | 31 | Configuration (wihout framework) 32 | -------------------------------- 33 | 34 | Step 1: Set up your User model 35 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 36 | 37 | .. note:: 38 | 39 | For the moment, only Doctrine ORM is supported. Doctrine ODM will be available soon. 40 | 41 | FOSMessage provides a flexible set of tools organized around three main entites: 42 | conversations, messages and persons. 43 | 44 | The library provides default entities for conversations and messages and they will 45 | be enough for the beginning (see *Customize the default entities* to learn more). 46 | 47 | However, you need to configure the library to tell it what your User model is. 48 | FOSMessage requires that your user class implement ``PersonInterface``. This 49 | library does not have any direct dependencies to any particular user system, 50 | except that it must implement the above interface. 51 | 52 | Your user class may look something like the following: 53 | 54 | .. code-block:: php 55 | 56 | id; 69 | } 70 | 71 | // Your code ... 72 | } 73 | 74 | Step 2: Configure the Doctrine entity manager 75 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 76 | 77 | You need to configure Doctrine for two things: 78 | 79 | - use your User model as the entity for FOSMessage ; 80 | - use the default entities provided by Doctrine ; 81 | 82 | If you are not using a framework, you need to configure Doctrine manually 83 | in order to get a usable EntityManager for FOSMessage. 84 | 85 | Here is an example of configuration to help you do so: 86 | 87 | .. code-block:: php 88 | 89 | setMetadataDriverImpl($config->newDefaultAnnotationDriver([ 97 | __DIR__ . '/vendor/friendsofsymfony/message/src/Driver/Doctrine/ORM/Entity', 98 | __DIR__ . '/src', 99 | ], false)); 100 | 101 | /* 102 | * If you want to use a debug logger 103 | */ 104 | if ($logger) { 105 | $config->setSQLLogger($logger); 106 | } 107 | 108 | /* 109 | * Your database parameters 110 | */ 111 | $dbParams = [ 112 | 'driver' => 'pdo_mysql', 113 | 'host' => '127.0.0.1', 114 | 'user' => 'root', 115 | 'password' => 'root', 116 | 'dbname' => 'fos_message', 117 | ]; 118 | 119 | /* 120 | * Use the Doctrine event manager to use your User model instead of the FOSMessage interface 121 | * in FOSMessage driver 122 | */ 123 | $rtel = new \Doctrine\ORM\Tools\ResolveTargetEntityListener(); 124 | $rtel->addResolveTargetEntity('FOS\\Message\\Model\\PersonInterface', 'Entity\\User', []); 125 | 126 | $evm = new \Doctrine\Common\EventManager(); 127 | $evm->addEventListener(Doctrine\ORM\Events::loadClassMetadata, $rtel); 128 | 129 | /* 130 | * Finally, create the Doctrine EntityManager 131 | */ 132 | $entityManager = \Doctrine\ORM\EntityManager::create($dbParams, $config, $evm); 133 | 134 | 135 | Configuration (using Symfony) 136 | ----------------------------- 137 | 138 | While the FOSMessage bundle is not ready, you can still configure Symfony and Doctrine to 139 | use the library in your project. 140 | 141 | Step 1: Set up your User model 142 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 143 | 144 | .. note:: 145 | 146 | For the moment, only Doctrine ORM is supported. Doctrine ODM will be available soon. 147 | 148 | FOSMessage provides a flexible set of tools organized around three main entites: 149 | conversations, messages and persons. 150 | 151 | The library provides default entities for conversations and messages and they will 152 | be enough for the beginning (see *Customize the default entities* to learn more). 153 | 154 | However, you need to configure the library to tell it what your User model is. 155 | FOSMessage requires that your user class implement ``PersonInterface``. This 156 | library does not have any direct dependencies to any particular user system, 157 | except that it must implement the above interface. 158 | 159 | Your user class may look something like the following: 160 | 161 | .. code-block:: php 162 | 163 | id; 178 | } 179 | 180 | // Your code ... 181 | } 182 | 183 | Step 2: Configure the Doctrine entity manager 184 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 185 | 186 | You need to configure Doctrine for two things: 187 | 188 | - use your User model as the entity for FOSMessage ; 189 | - use the default entities provided by Doctrine ; 190 | 191 | When you are using Symfony, you can configure the Doctrine entity manager through the 192 | DoctrineBundle configuration: 193 | 194 | .. code-block:: yml 195 | 196 | # app/config/config.yml 197 | 198 | doctrine: 199 | # ... 200 | 201 | orm: 202 | auto_generate_proxy_classes: "%kernel.debug%" 203 | naming_strategy: doctrine.orm.naming_strategy.underscore 204 | auto_mapping: true 205 | 206 | # The mappings to import the FOSMessage entities 207 | mappings: 208 | fos_message: 209 | type: annotation 210 | dir: %kernel.root_dir%/../vendor/friendsofsymfony/message/src/Driver/Doctrine/ORM/Entity 211 | prefix: FOS\Message\Driver\Doctrine\ORM\Entity 212 | 213 | # User your user entity instead of the PersonInterface 214 | resolve_target_entities: 215 | FOS\Message\Model\PersonInterface: AppBundle\Entity\User 216 | 217 | You also need to register a few services: 218 | 219 | .. code-block:: yml 220 | 221 | # app/config/services.yml 222 | 223 | services: 224 | fos_message.driver: 225 | class: FOS\Message\Driver\Doctrine\ORM\DoctrineORMDriver 226 | arguments: [ "@doctrine.orm.entity_manager" ] 227 | 228 | fos_message.repository: 229 | class: FOS\Message\Repository 230 | arguments: [ "@fos_message.driver" ] 231 | 232 | fos_message.event_dispatcher: 233 | class: FOS\Message\EventDispatcher\SymfonyBridgeEventDispatcher 234 | arguments: [ "@event_dispatcher" ] 235 | 236 | fos_message.tagger: 237 | class: FOS\Message\Tagger 238 | arguments: 239 | - "@fos_message.driver" 240 | - "@fos_message.repository" 241 | 242 | fos_message.sender: 243 | class: FOS\Message\Sender 244 | arguments: 245 | - "@fos_message.driver" 246 | - "@fos_message.event_dispatcher" 247 | 248 | And then you will be able to use the components as following: 249 | 250 | .. code-block:: php 251 | 252 | get('fos_message.repository'); 267 | $sender = $this->get('fos_message.sender'); 268 | 269 | return $this->render('default/index.html.twig'); 270 | } 271 | } 272 | 273 | 274 | Now that you have a configured entity manager, you are ready to start using the library! 275 | -------------------------------------------------------------------------------- /docs/book/usage.rst: -------------------------------------------------------------------------------- 1 | Usage 2 | ===== 3 | 4 | Once you have configured Doctrine and your model, you are ready to use FOSMessage. 5 | 6 | FOSMessage is organized around three components : the Repository that fetch conversations and messages, 7 | the Sender that start conversations and send replies and the Tagger that let you (the developer) tag 8 | conversations to retreive them in the future. 9 | 10 | These three components are usually set up automatically in the context of a framework (by the dependency 11 | injection). If you are not using a framework, you have to set up these components yourself. 12 | 13 | For the moment, as only Doctrine ORM is available in FOSMessage, you have to use the 14 | Doctrine ORM driver. In the future, other options will be available. 15 | 16 | 17 | Choose your driver 18 | ------------------ 19 | 20 | The driver is the object linking the library to your persistance layer (Doctrine ORM, Propel, etc.). 21 | Thus according to what persistance layer you are using, you have to choose a different driver 22 | for FOSMessage. 23 | 24 | For Doctrine ORM, you can create the driver as following using the entity manager configured in 25 | the **Getting started** chapter: 26 | 27 | .. code-block:: php 28 | 29 | getPersonConversations($this->getUser()); 44 | 45 | return $this->render('inbox.html.twig', [ 'conversations' => $conversations ]); 46 | } 47 | } 48 | 49 | 50 | Find a conversation by its identifier 51 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 52 | 53 | The method ``getConversation($id)`` is quite easy to understand: it returns a single conversation 54 | by its identifier (or null if none is found). 55 | 56 | .. note:: 57 | 58 | Note that the security is not handled by the library: you should check if your user is allowed 59 | to access the conversation. 60 | 61 | For instance, in a controller it could look like this: 62 | 63 | .. code-block:: php 64 | 65 | getConversation($id); 74 | 75 | // Check access 76 | if (! $conversation->isPersonInConversation($this->getUser())) { 77 | throw new AccessDeniedHttpException(); 78 | } 79 | 80 | return $this->render('conversation.html.twig', [ 'conversation' => $conversation ]); 81 | } 82 | } 83 | 84 | 85 | List the messages of a given conversation 86 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 87 | 88 | One you have a conversation, you will probably want to display its messages. To do so, you 89 | have to use the method 90 | ``getMessages(ConversationInterface $conversation, $offset = 0, $limit = 20, $sortDirection = 'ASC')``. 91 | 92 | This method has 4 arguments: 93 | 94 | - the conversation ``$conversation`` of the messages ; 95 | - the offset in the result set (for pagination) ; 96 | - the limit of messages to get (for pagination) ; 97 | - the sort direction to use (messages will be sorted by date) ; 98 | 99 | For instance, in a controller it could look like this: 100 | 101 | .. code-block:: php 102 | 103 | getConversation($id); 112 | 113 | // Check access 114 | if (! $conversation->isPersonInConversation($this->getUser())) { 115 | throw new AccessDeniedHttpException(); 116 | } 117 | 118 | $messages = $repository->getMessages($conversation); 119 | 120 | return $this->render('conversation.html.twig', [ 121 | 'conversation' => $conversation, 122 | 'messages' => $messages, 123 | ]); 124 | } 125 | } 126 | 127 | 128 | Find the link between a person and a conversation 129 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 130 | 131 | Sometimes you can need to retrieve the link between a user and a conversation 132 | (for instance if you customized the entities and stored data in this link). 133 | 134 | To do so, the repository provides the method 135 | ``getConversationPerson(ConversationInterface $conversation, PersonInterface $person)`` that 136 | will return you an instance of ``FOS\Message\ModelConversationPersonInterface``. 137 | -------------------------------------------------------------------------------- /docs/book/usage/sender.rst: -------------------------------------------------------------------------------- 1 | The Sender 2 | ========== 3 | 4 | The Sender let you start conversations and reply to them. 5 | 6 | You can create it like this: 7 | 8 | .. code-block:: php 9 | 10 | getMethod() == 'POST') { 46 | $data = ...; // Find the form data for instance ... 47 | 48 | $conversation = $sender->startConversation($this->getUser(), $data['recipient'], $data['body']); 49 | 50 | return $this->redirect('conversation_view', [ 'id' => $conversation->getId() ]); 51 | } 52 | 53 | return $this->render('form_start.html.twig'); 54 | } 55 | } 56 | 57 | 58 | Reply to a conversation 59 | ^^^^^^^^^^^^^^^^^^^^^^^ 60 | 61 | Once a user has started a conversation, other members could reply. The method 62 | ``sendMessage(ConversationInterface $conversation, PersonInterface $senderPerson, $body)`` 63 | does exactly that by replying to a given conversation, as a given sender with a given body. 64 | 65 | The method has 3 arguments: 66 | 67 | - ``$conversation``: the conversation in which the user want to post a reply ; 68 | - ``$senderPerson``: the user who wrote the message ; 69 | - ``$body``: the content of the reply ; 70 | 71 | This method return the created message object (instance of ``MessageInterface``). 72 | 73 | For instance, in a controller it could look like this: 74 | 75 | .. code-block:: php 76 | 77 | getConversation($id); 86 | 87 | // Check access 88 | if (! $conversation->isPersonInConversation($this->getUser())) { 89 | throw new AccessDeniedHttpException(); 90 | } 91 | 92 | $sender = new \FOS\Message\Sender($driver); 93 | 94 | if ($request->getMethod() == 'POST') { 95 | $data = ...; // Find the form data for instance ... 96 | 97 | $message = $sender->sendMessage($conversation, $this->getUser(), $data['body']); 98 | 99 | return $this->redirect('conversation_view', [ 'id' => $conversation->getId() ]); 100 | } 101 | 102 | return $this->render('form_reply.html.twig', [ 'conversation' => $conversation ]); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # FOSMessage documentation build configuration file, created by 4 | # sphinx-quickstart on Fri Nov 20 13:19:04 2015. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | import shlex 18 | import sphinx_rtd_theme 19 | 20 | # If extensions (or modules to document with autodoc) are in another directory, 21 | # add these directories to sys.path here. If the directory is relative to the 22 | # documentation root, use os.path.abspath to make it absolute, like shown here. 23 | #sys.path.insert(0, os.path.abspath('.')) 24 | 25 | # -- General configuration ------------------------------------------------ 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | #needs_sphinx = '1.0' 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = [] 34 | 35 | # Add any paths that contain templates here, relative to this directory. 36 | templates_path = ['_templates'] 37 | 38 | # The suffix(es) of source filenames. 39 | # You can specify multiple suffix as a list of string: 40 | # source_suffix = ['.rst', '.md'] 41 | source_suffix = '.rst' 42 | 43 | # The encoding of source files. 44 | #source_encoding = 'utf-8-sig' 45 | 46 | # The master toctree document. 47 | master_doc = 'index' 48 | 49 | # General information about the project. 50 | project = u'FOSMessage' 51 | copyright = u'2015, Titouan Galopin' 52 | author = u'Titouan Galopin' 53 | 54 | # The version info for the project you're documenting, acts as replacement for 55 | # |version| and |release|, also used in various other places throughout the 56 | # built documents. 57 | # 58 | # The short X.Y version. 59 | version = 'dev-master' 60 | # The full version, including alpha/beta/rc tags. 61 | release = '1.0.x-dev' 62 | 63 | # The language for content autogenerated by Sphinx. Refer to documentation 64 | # for a list of supported languages. 65 | # 66 | # This is also used if you do content translation via gettext catalogs. 67 | # Usually you set "language" from the command line for these cases. 68 | language = None 69 | 70 | # There are two options for replacing |today|: either, you set today to some 71 | # non-false value, then it is used: 72 | #today = '' 73 | # Else, today_fmt is used as the format for a strftime call. 74 | #today_fmt = '%B %d, %Y' 75 | 76 | # List of patterns, relative to source directory, that match files and 77 | # directories to ignore when looking for source files. 78 | exclude_patterns = ['_build'] 79 | 80 | # The reST default role (used for this markup: `text`) to use for all 81 | # documents. 82 | #default_role = None 83 | 84 | # If true, '()' will be appended to :func: etc. cross-reference text. 85 | #add_function_parentheses = True 86 | 87 | # If true, the current module name will be prepended to all description 88 | # unit titles (such as .. function::). 89 | #add_module_names = True 90 | 91 | # If true, sectionauthor and moduleauthor directives will be shown in the 92 | # output. They are ignored by default. 93 | #show_authors = False 94 | 95 | # The name of the Pygments (syntax highlighting) style to use. 96 | pygments_style = 'sphinx' 97 | 98 | # A list of ignored prefixes for module index sorting. 99 | #modindex_common_prefix = [] 100 | 101 | # If true, keep warnings as "system message" paragraphs in the built documents. 102 | #keep_warnings = False 103 | 104 | # If true, `todo` and `todoList` produce output, else they produce nothing. 105 | todo_include_todos = False 106 | 107 | 108 | # -- Options for HTML output ---------------------------------------------- 109 | 110 | # The theme to use for HTML and HTML Help pages. See the documentation for 111 | # a list of builtin themes. 112 | html_theme = 'sphinx_rtd_theme' 113 | 114 | # Theme options are theme-specific and customize the look and feel of a theme 115 | # further. For a list of options available for each theme, see the 116 | # documentation. 117 | #html_theme_options = {} 118 | 119 | # Add any paths that contain custom themes here, relative to this directory. 120 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 121 | 122 | # The name for this set of Sphinx documents. If None, it defaults to 123 | # " v documentation". 124 | #html_title = None 125 | 126 | # A shorter title for the navigation bar. Default is the same as html_title. 127 | #html_short_title = None 128 | 129 | # The name of an image file (relative to this directory) to place at the top 130 | # of the sidebar. 131 | #html_logo = None 132 | 133 | # The name of an image file (within the static path) to use as favicon of the 134 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 135 | # pixels large. 136 | #html_favicon = None 137 | 138 | # Add any paths that contain custom static files (such as style sheets) here, 139 | # relative to this directory. They are copied after the builtin static files, 140 | # so a file named "default.css" will overwrite the builtin "default.css". 141 | html_static_path = ['_static'] 142 | 143 | # Add any extra paths that contain custom files (such as robots.txt or 144 | # .htaccess) here, relative to this directory. These files are copied 145 | # directly to the root of the documentation. 146 | #html_extra_path = [] 147 | 148 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 149 | # using the given strftime format. 150 | #html_last_updated_fmt = '%b %d, %Y' 151 | 152 | # If true, SmartyPants will be used to convert quotes and dashes to 153 | # typographically correct entities. 154 | #html_use_smartypants = True 155 | 156 | # Custom sidebar templates, maps document names to template names. 157 | #html_sidebars = {} 158 | 159 | # Additional templates that should be rendered to pages, maps page names to 160 | # template names. 161 | #html_additional_pages = {} 162 | 163 | # If false, no module index is generated. 164 | #html_domain_indices = True 165 | 166 | # If false, no index is generated. 167 | #html_use_index = True 168 | 169 | # If true, the index is split into individual pages for each letter. 170 | #html_split_index = False 171 | 172 | # If true, links to the reST sources are added to the pages. 173 | #html_show_sourcelink = True 174 | 175 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 176 | #html_show_sphinx = True 177 | 178 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 179 | #html_show_copyright = True 180 | 181 | # If true, an OpenSearch description file will be output, and all pages will 182 | # contain a tag referring to it. The value of this option must be the 183 | # base URL from which the finished HTML is served. 184 | #html_use_opensearch = '' 185 | 186 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 187 | #html_file_suffix = None 188 | 189 | # Language to be used for generating the HTML full-text search index. 190 | # Sphinx supports the following languages: 191 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 192 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 193 | #html_search_language = 'en' 194 | 195 | # A dictionary with options for the search language support, empty by default. 196 | # Now only 'ja' uses this config value 197 | #html_search_options = {'type': 'default'} 198 | 199 | # The name of a javascript file (relative to the configuration directory) that 200 | # implements a search results scorer. If empty, the default will be used. 201 | #html_search_scorer = 'scorer.js' 202 | 203 | # Output file base name for HTML help builder. 204 | htmlhelp_basename = 'FOSMessagedoc' 205 | 206 | # -- Options for LaTeX output --------------------------------------------- 207 | 208 | latex_elements = { 209 | # The paper size ('letterpaper' or 'a4paper'). 210 | #'papersize': 'letterpaper', 211 | 212 | # The font size ('10pt', '11pt' or '12pt'). 213 | #'pointsize': '10pt', 214 | 215 | # Additional stuff for the LaTeX preamble. 216 | #'preamble': '', 217 | 218 | # Latex figure (float) alignment 219 | #'figure_align': 'htbp', 220 | } 221 | 222 | # Grouping the document tree into LaTeX files. List of tuples 223 | # (source start file, target name, title, 224 | # author, documentclass [howto, manual, or own class]). 225 | latex_documents = [ 226 | (master_doc, 'FOSMessage.tex', u'FOSMessage Documentation', 227 | u'Titouan Galopin', 'manual'), 228 | ] 229 | 230 | # The name of an image file (relative to this directory) to place at the top of 231 | # the title page. 232 | #latex_logo = None 233 | 234 | # For "manual" documents, if this is true, then toplevel headings are parts, 235 | # not chapters. 236 | #latex_use_parts = False 237 | 238 | # If true, show page references after internal links. 239 | #latex_show_pagerefs = False 240 | 241 | # If true, show URL addresses after external links. 242 | #latex_show_urls = False 243 | 244 | # Documents to append as an appendix to all manuals. 245 | #latex_appendices = [] 246 | 247 | # If false, no module index is generated. 248 | #latex_domain_indices = True 249 | 250 | 251 | # -- Options for manual page output --------------------------------------- 252 | 253 | # One entry per manual page. List of tuples 254 | # (source start file, name, description, authors, manual section). 255 | man_pages = [ 256 | (master_doc, 'fosmessage', u'FOSMessage Documentation', 257 | [author], 1) 258 | ] 259 | 260 | # If true, show URL addresses after external links. 261 | #man_show_urls = False 262 | 263 | 264 | # -- Options for Texinfo output ------------------------------------------- 265 | 266 | # Grouping the document tree into Texinfo files. List of tuples 267 | # (source start file, target name, title, author, 268 | # dir menu entry, description, category) 269 | texinfo_documents = [ 270 | (master_doc, 'FOSMessage', u'FOSMessage Documentation', 271 | author, 'FOSMessage', 'One line description of project.', 272 | 'Miscellaneous'), 273 | ] 274 | 275 | # Documents to append as an appendix to all manuals. 276 | #texinfo_appendices = [] 277 | 278 | # If false, no module index is generated. 279 | #texinfo_domain_indices = True 280 | 281 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 282 | #texinfo_show_urls = 'footnote' 283 | 284 | # If true, do not generate a @detailmenu in the "Top" node's menu. 285 | #texinfo_no_detailmenu = False 286 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | FOSMessage 2 | ========== 3 | 4 | FOSMessage is a PHP 5.4+ framework-agnostic library providing a data structure 5 | and common features to set up user-to-user messaging systems. 6 | 7 | You can think of it as a model for your messaging features : it will take care of the consistency 8 | of the data for you in order to easily create a full-featured messaging system. 9 | 10 | .. note:: 11 | 12 | This library is currently in development. You can test it in your project 13 | (the Composer installation process is very simple), but you should not use it in production 14 | for the moment. 15 | 16 | This library is based on concepts shared by most modern frameworks (dependency injection, 17 | event dispatching, abstract data drivers, etc.) and therefore, it’s very easy to set it up in 18 | any kind of context. 19 | 20 | If you want to set it up in Symfony, *FOSMesageBundle* is being developed in a new version 21 | (not ready yet). 22 | 23 | **Key features** 24 | 25 | - Conversation-based messaging 26 | - Multiple conversations participants support 27 | - Very easy to implement (at least in most of the cases) 28 | - Framework-agnotic 29 | - Doctrine ORM and Mongo ODM support 30 | - Not linked to user system implementation 31 | - Optionnal tagging system to organize conversations 32 | - Event system to let developer execute actions on key steps 33 | - Implemented in framework-specific bundle / module 34 | - PHP7 and HHVM support 35 | 36 | Documentation 37 | ------------- 38 | 39 | .. toctree:: 40 | :maxdepth: 3 41 | 42 | book/getting_started 43 | book/usage 44 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | tests/ 15 | 16 | 17 | 18 | 19 | 20 | src/ 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Driver/AbstractDriver.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Driver; 13 | 14 | use FOS\Message\Model\ConversationInterface; 15 | use FOS\Message\Model\MessageInterface; 16 | use FOS\Message\Model\PersonInterface; 17 | 18 | /** 19 | * Abstract driver providing model class customization. 20 | * 21 | * @author Titouan Galopin 22 | */ 23 | abstract class AbstractDriver implements DriverInterface 24 | { 25 | /** 26 | * @var string 27 | */ 28 | private $conversationClass; 29 | 30 | /** 31 | * @var string 32 | */ 33 | private $conversationPersonClass; 34 | 35 | /** 36 | * @var string 37 | */ 38 | private $messageClass; 39 | 40 | /** 41 | * @var string 42 | */ 43 | private $messagePersonClass; 44 | 45 | /** 46 | * Constructor. 47 | * 48 | * @param string $conversationClass 49 | * @param string $conversationPersonClass 50 | * @param string $messageClass 51 | * @param string $messagePersonClass 52 | */ 53 | public function __construct($conversationClass, $conversationPersonClass, $messageClass, $messagePersonClass) 54 | { 55 | $this->conversationClass = $conversationClass; 56 | $this->conversationPersonClass = $conversationPersonClass; 57 | $this->messageClass = $messageClass; 58 | $this->messagePersonClass = $messagePersonClass; 59 | } 60 | 61 | /** 62 | * @return string 63 | */ 64 | public function getConversationClass() 65 | { 66 | return $this->conversationClass; 67 | } 68 | 69 | /** 70 | * @return string 71 | */ 72 | public function getConversationPersonClass() 73 | { 74 | return $this->conversationPersonClass; 75 | } 76 | 77 | /** 78 | * @return string 79 | */ 80 | public function getMessageClass() 81 | { 82 | return $this->messageClass; 83 | } 84 | 85 | /** 86 | * @return string 87 | */ 88 | public function getMessagePersonClass() 89 | { 90 | return $this->messagePersonClass; 91 | } 92 | 93 | /** 94 | * {@inheritdoc} 95 | */ 96 | public function createConversationModel() 97 | { 98 | $class = $this->conversationClass; 99 | 100 | return new $class(); 101 | } 102 | 103 | /** 104 | * {@inheritdoc} 105 | */ 106 | public function createConversationPersonModel(ConversationInterface $conversation, PersonInterface $person) 107 | { 108 | $class = $this->conversationPersonClass; 109 | 110 | return new $class($conversation, $person); 111 | } 112 | 113 | /** 114 | * {@inheritdoc} 115 | */ 116 | public function createMessageModel(ConversationInterface $conversation, PersonInterface $sender, $body) 117 | { 118 | $class = $this->messageClass; 119 | 120 | return new $class($conversation, $sender, $body); 121 | } 122 | 123 | /** 124 | * {@inheritdoc} 125 | */ 126 | public function createMessagePersonModel(MessageInterface $message, PersonInterface $person) 127 | { 128 | $class = $this->messagePersonClass; 129 | 130 | return new $class($message, $person); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/Driver/Doctrine/AbstractDoctrineDriver.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Driver\Doctrine; 13 | 14 | use Doctrine\Common\Persistence\ObjectManager; 15 | use Doctrine\ORM\EntityManager; 16 | use FOS\Message\Driver\AbstractDriver; 17 | use FOS\Message\Model\ConversationInterface; 18 | use FOS\Message\Model\ConversationPersonInterface; 19 | use FOS\Message\Model\MessageInterface; 20 | use FOS\Message\Model\MessagePersonInterface; 21 | 22 | /** 23 | * Abstract driver for Doctrine persistence managers (ORM and ODM). 24 | * 25 | * @author Titouan Galopin 26 | */ 27 | abstract class AbstractDoctrineDriver extends AbstractDriver 28 | { 29 | /** 30 | * @var ObjectManager|EntityManager 31 | */ 32 | protected $objectManager; 33 | 34 | /** 35 | * Constructor. 36 | * 37 | * @param ObjectManager $objectManager 38 | * @param string $conversationClass 39 | * @param string $conversationPersonClass 40 | * @param string $messageClass 41 | * @param string $messagePersonClass 42 | */ 43 | public function __construct( 44 | ObjectManager $objectManager, 45 | $conversationClass, 46 | $conversationPersonClass, 47 | $messageClass, 48 | $messagePersonClass 49 | ) { 50 | parent::__construct($conversationClass, $conversationPersonClass, $messageClass, $messagePersonClass); 51 | 52 | $this->objectManager = $objectManager; 53 | } 54 | 55 | /** 56 | * {@inheritdoc} 57 | */ 58 | public function persistConversation(ConversationInterface $conversation) 59 | { 60 | $this->objectManager->persist($conversation); 61 | } 62 | 63 | /** 64 | * {@inheritdoc} 65 | */ 66 | public function persistConversationPerson(ConversationPersonInterface $conversationPerson) 67 | { 68 | $this->objectManager->persist($conversationPerson); 69 | } 70 | 71 | /** 72 | * {@inheritdoc} 73 | */ 74 | public function persistMessage(MessageInterface $message) 75 | { 76 | $this->objectManager->persist($message); 77 | } 78 | 79 | /** 80 | * {@inheritdoc} 81 | */ 82 | public function persistMessagePerson(MessagePersonInterface $messagePerson) 83 | { 84 | $this->objectManager->persist($messagePerson); 85 | } 86 | 87 | /** 88 | * {@inheritdoc} 89 | */ 90 | public function flush() 91 | { 92 | $this->objectManager->flush(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Driver/Doctrine/ORM/DoctrineORMDriver.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Driver\Doctrine\ORM; 13 | 14 | use Doctrine\Common\Collections\ArrayCollection; 15 | use Doctrine\ORM\EntityManager; 16 | use FOS\Message\Driver\Doctrine\AbstractDoctrineDriver; 17 | use FOS\Message\Model\ConversationInterface; 18 | use FOS\Message\Model\PersonInterface; 19 | use FOS\Message\Model\TagInterface; 20 | use Symfony\Component\VarDumper\VarDumper; 21 | 22 | /** 23 | * Driver for Doctrine ORM. 24 | * 25 | * @author Titouan Galopin 26 | */ 27 | class DoctrineORMDriver extends AbstractDoctrineDriver 28 | { 29 | /** 30 | * Constructor. 31 | * 32 | * @param EntityManager $objectManager 33 | * @param string $conversationClass 34 | * @param string $conversationPersonClass 35 | * @param string $messageClass 36 | * @param string $messagePersonClass 37 | */ 38 | public function __construct( 39 | EntityManager $objectManager, 40 | $conversationClass = 'FOS\Message\Driver\Doctrine\ORM\Entity\Conversation', 41 | $conversationPersonClass = 'FOS\Message\Driver\Doctrine\ORM\Entity\ConversationPerson', 42 | $messageClass = 'FOS\Message\Driver\Doctrine\ORM\Entity\Message', 43 | $messagePersonClass = 'FOS\Message\Driver\Doctrine\ORM\Entity\MessagePerson' 44 | ) { 45 | parent::__construct( 46 | $objectManager, 47 | $conversationClass, 48 | $conversationPersonClass, 49 | $messageClass, 50 | $messagePersonClass 51 | ); 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | public function findPersonConversations(PersonInterface $person, TagInterface $tag = null) 58 | { 59 | $qb = $this->createConversationQueryBuilder(); 60 | 61 | // Last message date order 62 | $lastMessageDateSubquery = $this->objectManager->createQueryBuilder() 63 | ->select('MAX(lmds.read)') 64 | ->from($this->getMessagePersonClass(), 'lmds') 65 | ->leftJoin('lmds.message', 'lmdsm') 66 | ->where('lmds.person = :person') 67 | ->andWhere('IDENTITY(lmdsm.conversation) = c.id') 68 | ->getDQL(); 69 | 70 | $qb->addSelect('('. $lastMessageDateSubquery .') AS HIDDEN lastMessageDate'); 71 | $qb->orderBy('lastMessageDate', 'DESC'); 72 | 73 | // Person filter 74 | $personSubquery = $this->objectManager->createQueryBuilder() 75 | ->select('IDENTITY(pfcp.conversation)') 76 | ->from($this->getConversationPersonClass(), 'pfcp') 77 | ->where('pfcp.person = :person') 78 | ->getDQL(); 79 | 80 | $qb->where($qb->expr()->in('c.id', $personSubquery)); 81 | $qb->setParameter('person', $person->getId()); 82 | 83 | // Tag filter 84 | if ($tag !== null) { 85 | $tag = $tag->getName(); 86 | 87 | $tagSubquerey = $this->objectManager->createQueryBuilder() 88 | ->select('IDENTITY(tfcp.conversation)') 89 | ->from($this->getConversationPersonClass(), 'tfcp') 90 | ->leftJoin('tfcp.tags', 'tft') 91 | ->where('tft.name = :tag') 92 | ->getDQL(); 93 | 94 | $qb->andWhere($qb->expr()->in('c.id', $tagSubquerey)); 95 | $qb->setParameter('tag', $tag); 96 | } 97 | 98 | return new ArrayCollection($qb->getQuery()->getResult()); 99 | } 100 | 101 | /** 102 | * {@inheritdoc} 103 | */ 104 | public function findConversationPerson(ConversationInterface $conversation, PersonInterface $person) 105 | { 106 | return $this->objectManager->createQueryBuilder() 107 | ->select('cp', 'c', 'p', 't') 108 | ->from($this->getConversationPersonClass(), 'cp') 109 | ->innerJoin('cp.conversation', 'c') 110 | ->innerJoin('cp.person', 'p') 111 | ->leftJoin('cp.tags', 't') 112 | ->where('c.id = :conversation') 113 | ->setParameter('conversation', $conversation->getId()) 114 | ->andWhere('p.id = :person') 115 | ->setParameter('person', $person->getId()) 116 | ->setMaxResults(1) 117 | ->getQuery() 118 | ->getOneOrNullResult(); 119 | } 120 | 121 | /** 122 | * {@inheritdoc} 123 | */ 124 | public function findConversation($id) 125 | { 126 | return $this->createConversationQueryBuilder() 127 | ->where('c.id = :id') 128 | ->setParameter('id', $id) 129 | ->getQuery() 130 | ->getOneOrNullResult(); 131 | } 132 | 133 | /** 134 | * {@inheritdoc} 135 | */ 136 | public function findMessages(ConversationInterface $conversation, $offset = 0, $limit = 20, $sortDirection = 'ASC') 137 | { 138 | $qb = $this->objectManager->createQueryBuilder() 139 | ->select('m', 's') 140 | ->from($this->getMessageClass(), 'm') 141 | ->leftJoin('m.sender', 's') 142 | ->where('m.conversation = :conversation') 143 | ->setParameter('conversation', $conversation->getId()) 144 | ->setMaxResults($limit) 145 | ->setFirstResult($offset) 146 | ->orderBy('m.date', $sortDirection) 147 | ->addOrderBy('m.id', $sortDirection); 148 | 149 | return new ArrayCollection($qb->getQuery()->getResult()); 150 | } 151 | 152 | /** 153 | * Create a conversation query builder with optimized joins. 154 | * 155 | * @return \Doctrine\ORM\QueryBuilder 156 | */ 157 | private function createConversationQueryBuilder() 158 | { 159 | return $this->objectManager->createQueryBuilder() 160 | ->select('c', 'cp', 'p', 't', 'm', 'mp') 161 | ->from($this->getConversationClass(), 'c') 162 | ->leftJoin('c.persons', 'cp') 163 | ->leftJoin('cp.person', 'p') 164 | ->leftJoin('cp.tags', 't') 165 | ->leftJoin('c.messages', 'm') 166 | ->leftJoin('m.persons', 'mp') 167 | ->orderBy('m.date', 'ASC') 168 | ->addOrderBy('m.id', 'ASC'); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/Driver/Doctrine/ORM/Entity/Conversation.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Driver\Doctrine\ORM\Entity; 13 | 14 | use Doctrine\Common\Collections\Collection; 15 | use Doctrine\ORM\Mapping as ORM; 16 | use FOS\Message\Model\Conversation as BaseConversation; 17 | use FOS\Message\Model\ConversationPersonInterface; 18 | use FOS\Message\Model\MessageInterface; 19 | 20 | /** 21 | * @ORM\Table(name="fos_message_conversations") 22 | * @ORM\Entity 23 | */ 24 | class Conversation extends BaseConversation 25 | { 26 | /** 27 | * @ORM\Id 28 | * @ORM\Column(type="integer") 29 | * @ORM\GeneratedValue(strategy="AUTO") 30 | */ 31 | protected $id; 32 | 33 | /** 34 | * @var string 35 | * 36 | * @ORM\Column(type="string", length=255, nullable=true) 37 | */ 38 | protected $subject; 39 | 40 | /** 41 | * @var MessageInterface[]|Collection 42 | * 43 | * @ORM\OneToMany( 44 | * targetEntity="FOS\Message\Driver\Doctrine\ORM\Entity\Message", 45 | * mappedBy="conversation", 46 | * cascade={"all"} 47 | * ) 48 | */ 49 | protected $messages; 50 | 51 | /** 52 | * @var ConversationPersonInterface[]|Collection 53 | * 54 | * @ORM\OneToMany( 55 | * targetEntity="FOS\Message\Driver\Doctrine\ORM\Entity\ConversationPerson", 56 | * mappedBy="conversation", 57 | * cascade={"all"} 58 | * ) 59 | */ 60 | protected $persons; 61 | } 62 | -------------------------------------------------------------------------------- /src/Driver/Doctrine/ORM/Entity/ConversationPerson.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Driver\Doctrine\ORM\Entity; 13 | 14 | use Doctrine\ORM\Mapping as ORM; 15 | use FOS\Message\Model\ConversationPerson as BaseConversationPerson; 16 | 17 | /** 18 | * @ORM\Table(name="fos_message_conversations_persons") 19 | * @ORM\Entity 20 | */ 21 | class ConversationPerson extends BaseConversationPerson 22 | { 23 | /** 24 | * @ORM\Id 25 | * @ORM\Column(type="integer") 26 | * @ORM\GeneratedValue(strategy="AUTO") 27 | */ 28 | protected $id; 29 | 30 | /** 31 | * @var \FOS\Message\Model\ConversationInterface 32 | * 33 | * @ORM\ManyToOne( 34 | * targetEntity="FOS\Message\Driver\Doctrine\ORM\Entity\Conversation", 35 | * inversedBy="persons", 36 | * cascade={"all"} 37 | * ) 38 | */ 39 | protected $conversation; 40 | 41 | /** 42 | * @var \FOS\Message\Model\PersonInterface 43 | * 44 | * @ORM\ManyToOne(targetEntity="FOS\Message\Model\PersonInterface", cascade={"all"}) 45 | */ 46 | protected $person; 47 | 48 | /** 49 | * @var \FOS\Message\Model\TagInterface[]|\Doctrine\Common\Collections\ArrayCollection 50 | * 51 | * @ORM\ManyToMany(targetEntity="FOS\Message\Driver\Doctrine\ORM\Entity\Tag") 52 | * @ORM\JoinTable(name="fos_message_conversations_persons_tags") 53 | */ 54 | protected $tags; 55 | } 56 | -------------------------------------------------------------------------------- /src/Driver/Doctrine/ORM/Entity/Message.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Driver\Doctrine\ORM\Entity; 13 | 14 | use Doctrine\ORM\Mapping as ORM; 15 | use FOS\Message\Model\Message as BaseMessage; 16 | 17 | /** 18 | * @ORM\Table(name="fos_message_messages") 19 | * @ORM\Entity 20 | */ 21 | class Message extends BaseMessage 22 | { 23 | /** 24 | * @ORM\Id 25 | * @ORM\Column(type="integer") 26 | * @ORM\GeneratedValue(strategy="AUTO") 27 | */ 28 | protected $id; 29 | 30 | /** 31 | * @var \FOS\Message\Model\ConversationInterface 32 | * 33 | * @ORM\ManyToOne( 34 | * targetEntity="FOS\Message\Driver\Doctrine\ORM\Entity\Conversation", 35 | * inversedBy="messages", 36 | * cascade={"all"} 37 | * ) 38 | */ 39 | protected $conversation; 40 | 41 | /** 42 | * @var \FOS\Message\Model\PersonInterface 43 | * 44 | * @ORM\ManyToOne(targetEntity="FOS\Message\Model\PersonInterface", cascade={"all"}) 45 | */ 46 | protected $sender; 47 | 48 | /** 49 | * @var string 50 | * 51 | * @ORM\Column(type="text") 52 | */ 53 | protected $body; 54 | 55 | /** 56 | * @var \DateTime 57 | * 58 | * @ORM\Column(type="datetime") 59 | */ 60 | protected $date; 61 | 62 | /** 63 | * @var \FOS\Message\Model\MessagePersonInterface[] 64 | * 65 | * @ORM\OneToMany( 66 | * targetEntity="FOS\Message\Driver\Doctrine\ORM\Entity\MessagePerson", 67 | * mappedBy="message", 68 | * cascade={"all"} 69 | * ) 70 | */ 71 | protected $persons; 72 | } 73 | -------------------------------------------------------------------------------- /src/Driver/Doctrine/ORM/Entity/MessagePerson.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Driver\Doctrine\ORM\Entity; 13 | 14 | use Doctrine\ORM\Mapping as ORM; 15 | use FOS\Message\Model\MessagePerson as BaseMessagePerson; 16 | 17 | /** 18 | * @ORM\Table(name="fos_message_messages_persons") 19 | * @ORM\Entity 20 | */ 21 | class MessagePerson extends BaseMessagePerson 22 | { 23 | /** 24 | * @ORM\Id 25 | * @ORM\Column(type="integer") 26 | * @ORM\GeneratedValue(strategy="AUTO") 27 | */ 28 | protected $id; 29 | 30 | /** 31 | * @var \FOS\Message\Model\MessageInterface 32 | * 33 | * @ORM\ManyToOne( 34 | * targetEntity="FOS\Message\Driver\Doctrine\ORM\Entity\Message", 35 | * inversedBy="persons", 36 | * cascade={"all"} 37 | * ) 38 | */ 39 | protected $message; 40 | 41 | /** 42 | * @var \FOS\Message\Model\PersonInterface 43 | * 44 | * @ORM\ManyToOne(targetEntity="FOS\Message\Model\PersonInterface", cascade={"all"}) 45 | */ 46 | protected $person; 47 | 48 | /** 49 | * @var \DateTime 50 | * 51 | * @ORM\Column(type="datetime", name="read_date", nullable=true) 52 | */ 53 | protected $read; 54 | } 55 | -------------------------------------------------------------------------------- /src/Driver/Doctrine/ORM/Entity/Tag.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Driver\Doctrine\ORM\Entity; 13 | 14 | use Doctrine\ORM\Mapping as ORM; 15 | use FOS\Message\Model\TagInterface; 16 | use Webmozart\Assert\Assert; 17 | 18 | /** 19 | * @ORM\Table(name="fos_message_tags") 20 | * @ORM\Entity 21 | */ 22 | class Tag implements TagInterface 23 | { 24 | /** 25 | * @ORM\Id 26 | * @ORM\Column(type="integer") 27 | * @ORM\GeneratedValue(strategy="AUTO") 28 | */ 29 | protected $id; 30 | 31 | /** 32 | * @var string 33 | * 34 | * @ORM\Column(type="string", length=100) 35 | */ 36 | protected $name; 37 | 38 | /** 39 | * @param string $name 40 | */ 41 | public function __construct($name) 42 | { 43 | $this->name = $name; 44 | } 45 | 46 | /** 47 | * @return int 48 | */ 49 | public function getId() 50 | { 51 | return $this->id; 52 | } 53 | 54 | /** 55 | * @return string 56 | */ 57 | public function getName() 58 | { 59 | return $this->name; 60 | } 61 | 62 | /** 63 | * @param string $name 64 | */ 65 | public function setName($name) 66 | { 67 | Assert::string($name); 68 | 69 | $this->name = $name; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Driver/DriverInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Driver; 13 | 14 | use Doctrine\Common\Collections\Collection; 15 | use FOS\Message\Model\ConversationInterface; 16 | use FOS\Message\Model\ConversationPersonInterface; 17 | use FOS\Message\Model\MessageInterface; 18 | use FOS\Message\Model\MessagePersonInterface; 19 | use FOS\Message\Model\PersonInterface; 20 | use FOS\Message\Model\TagInterface; 21 | 22 | /** 23 | * A driver is a bridge between the FOSMessage library and a 24 | * persistance layer used to save and retreive messages and conversations. 25 | * 26 | * @author Titouan Galopin 27 | * @author Christian Riesen 28 | */ 29 | interface DriverInterface 30 | { 31 | /** 32 | * Create and return a conversation object. 33 | * 34 | * @return ConversationInterface The conversation object. 35 | */ 36 | public function createConversationModel(); 37 | 38 | /** 39 | * Create a ConversationPerson model object. 40 | * 41 | * @param ConversationInterface $conversation 42 | * @param PersonInterface $person 43 | * 44 | * @return ConversationPersonInterface 45 | */ 46 | public function createConversationPersonModel(ConversationInterface $conversation, PersonInterface $person); 47 | 48 | /** 49 | * Create and return a message object. 50 | * 51 | * @param ConversationInterface $conversation The conversation the message belongs to. 52 | * @param PersonInterface $sender 53 | * @param string $body 54 | * 55 | * @return MessageInterface 56 | */ 57 | public function createMessageModel(ConversationInterface $conversation, PersonInterface $sender, $body); 58 | 59 | /** 60 | * Create a MessagePerson model object. 61 | * 62 | * @param MessageInterface $message 63 | * @param PersonInterface $person 64 | * 65 | * @return MessagePersonInterface 66 | */ 67 | public function createMessagePersonModel(MessageInterface $message, PersonInterface $person); 68 | 69 | /** 70 | * Return the list of conversations of one person 71 | * An optionnal tag can be specified to filter the list. 72 | * 73 | * @param PersonInterface $person 74 | * @param TagInterface|null $tag 75 | * 76 | * @return PersonInterface[]|Collection 77 | */ 78 | public function findPersonConversations(PersonInterface $person, TagInterface $tag = null); 79 | 80 | /** 81 | * Return a single conversation by its identifier or null if 82 | * no conversation is found. 83 | * 84 | * @param int $id 85 | * 86 | * @return ConversationInterface|null 87 | */ 88 | public function findConversation($id); 89 | 90 | /** 91 | * Return a single ConversationPerson entity corresponding to the link 92 | * between the given conversation and the given person or null if no 93 | * such link is found. 94 | * 95 | * @param ConversationInterface $conversation 96 | * @param PersonInterface $person 97 | * 98 | * @return ConversationPersonInterface|null 99 | */ 100 | public function findConversationPerson(ConversationInterface $conversation, PersonInterface $person); 101 | 102 | /** 103 | * Return the ordered list of messages in a conversation. 104 | * 105 | * @param ConversationInterface $conversation 106 | * @param int $limit 107 | * @param int $offset 108 | * @param string $sortDirection 109 | * 110 | * @return MessageInterface[]|Collection The messages 111 | */ 112 | public function findMessages(ConversationInterface $conversation, $offset = 0, $limit = 20, $sortDirection = 'ASC'); 113 | 114 | /** 115 | * Persist a conversation in the persistance layer. 116 | * The flush method will be called later so this method can rely 117 | * on it to really write into the persistance layer. 118 | * 119 | * @param ConversationInterface $conversation The conversation to persist 120 | * 121 | * @return bool True if the save succeed, false otherwise 122 | */ 123 | public function persistConversation(ConversationInterface $conversation); 124 | 125 | /** 126 | * Persist a ConversationPerson object in the persistance layer. 127 | * The flush method will be called later so this method can rely 128 | * on it to really write into the persistance layer. 129 | * 130 | * @param ConversationPersonInterface $conversationPerson The entity to persist 131 | * 132 | * @return bool True if the save succeed, false otherwise 133 | */ 134 | public function persistConversationPerson(ConversationPersonInterface $conversationPerson); 135 | 136 | /** 137 | * Persist a message in the persistance layer. 138 | * The flush method will be called later so this method can rely 139 | * on it to really write into the persistance layer. 140 | * 141 | * @param MessageInterface $message The message to persist 142 | * 143 | * @return bool True if the save succeed, false otherwise 144 | */ 145 | public function persistMessage(MessageInterface $message); 146 | 147 | /** 148 | * Persist a MessagePerson in the persistance layer. 149 | * The flush method will be called later so this method can rely 150 | * on it to really write into the persistance layer. 151 | * 152 | * @param MessagePersonInterface $messagePerson The entity to persist 153 | * 154 | * @return bool True if the save succeed, false otherwise 155 | */ 156 | public function persistMessagePerson(MessagePersonInterface $messagePerson); 157 | 158 | /** 159 | * Flush the previous `persistXXX()` calls by really writing in the 160 | * persistance layer. 161 | */ 162 | public function flush(); 163 | } 164 | -------------------------------------------------------------------------------- /src/Event/ConversationEvent.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Event; 13 | 14 | use FOS\Message\Model\ConversationInterface; 15 | use FOS\Message\Model\MessageInterface; 16 | 17 | /** 18 | * Payload dispatched by the EventDispatcher when a conversation is started. 19 | * 20 | * @author Titouan Galopin 21 | */ 22 | class ConversationEvent extends MessageEvent 23 | { 24 | /** 25 | * @var ConversationInterface 26 | */ 27 | private $conversation; 28 | 29 | /** 30 | * @param ConversationInterface $conversation 31 | * @param MessageInterface $message 32 | */ 33 | public function __construct(ConversationInterface $conversation, MessageInterface $message) 34 | { 35 | parent::__construct($message); 36 | 37 | $this->conversation = $conversation; 38 | } 39 | 40 | /** 41 | * @return ConversationInterface 42 | */ 43 | public function getConversation() 44 | { 45 | return $this->conversation; 46 | } 47 | 48 | /** 49 | * @param ConversationInterface $conversation 50 | */ 51 | public function setConversation(ConversationInterface $conversation) 52 | { 53 | $this->conversation = $conversation; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Event/MessageEvent.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Event; 13 | 14 | use FOS\Message\Model\MessageInterface; 15 | 16 | /** 17 | * Payload dispatched by the EventDispatcher when an answer is sent in a conversation. 18 | * 19 | * @author Titouan Galopin 20 | */ 21 | class MessageEvent 22 | { 23 | /** 24 | * @var MessageInterface 25 | */ 26 | private $message; 27 | 28 | /** 29 | * @param MessageInterface $message 30 | */ 31 | public function __construct(MessageInterface $message) 32 | { 33 | $this->message = $message; 34 | } 35 | 36 | /** 37 | * @return MessageInterface 38 | */ 39 | public function getMessage() 40 | { 41 | return $this->message; 42 | } 43 | 44 | /** 45 | * @param MessageInterface $message 46 | */ 47 | public function setMessage(MessageInterface $message) 48 | { 49 | $this->message = $message; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Event/SymfonyEvent.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Event; 13 | 14 | use Symfony\Component\EventDispatcher\Event; 15 | 16 | /** 17 | * @author Abdellatif Ait boudad 18 | */ 19 | class SymfonyEvent extends Event 20 | { 21 | /** 22 | * @var MessageEvent 23 | */ 24 | private $event; 25 | 26 | /** 27 | * @param MessageEvent $event 28 | */ 29 | public function __construct(MessageEvent $event) 30 | { 31 | $this->event = $event; 32 | } 33 | 34 | /** 35 | * @return MessageEvent 36 | */ 37 | public function getOriginalEvent() 38 | { 39 | return $this->event; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/EventDispatcher/EventDispatcherInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\EventDispatcher; 13 | 14 | use FOS\Message\Event\ConversationEvent; 15 | use FOS\Message\Event\MessageEvent; 16 | 17 | /** 18 | * EventDispatcher to notify changes to the external tools. 19 | * 20 | * @author Titouan Galopin 21 | */ 22 | interface EventDispatcherInterface 23 | { 24 | /** 25 | * START_CONVERSATION_PRE_PERSIST is dispatched right before entities 26 | * are persisted and flushed in the Sender::startConversation() method. 27 | */ 28 | const START_CONVERSATION_PRE_PERSIST = 'fos_message.start_conversation.pre_persist'; 29 | 30 | /** 31 | * START_CONVERSATION_POST_PERSIST is dispatched right after entities 32 | * are persisted and flushed in the Sender::startConversation() method. 33 | */ 34 | const START_CONVERSATION_POST_PERSIST = 'fos_message.start_conversation.post_persist'; 35 | 36 | /** 37 | * SEND_MESSAGE_PRE_PERSIST is dispatched right before entities 38 | * are persisted and flushed in the Sender::sendMessage() method. 39 | */ 40 | const SEND_MESSAGE_PRE_PERSIST = 'fos_message.send_message.pre_persist'; 41 | 42 | /** 43 | * SEND_MESSAGE_POST_PERSIST is dispatched right after entities 44 | * are persisted and flushed in the Sender::sendMessage() method. 45 | */ 46 | const SEND_MESSAGE_POST_PERSIST = 'fos_message.send_message.post_persist'; 47 | 48 | /** 49 | * Dispatch an event and return the result. 50 | * 51 | * A ConversationEvent is dispatched when a conversation is started. 52 | * A MessageEvent is dispatched when a message is sent in a conversation. 53 | * 54 | * See the EventDis dispatched by FOSMessage. 55 | * 56 | * @param string $eventName 57 | * @param MessageEvent|ConversationEvent $event 58 | * 59 | * @return MessageEvent 60 | */ 61 | public function dispatch($eventName, MessageEvent $event); 62 | } 63 | -------------------------------------------------------------------------------- /src/EventDispatcher/NativeEventDispatcher.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\EventDispatcher; 13 | 14 | use FOS\Message\Event\MessageEvent; 15 | use Webmozart\Assert\Assert; 16 | 17 | /** 18 | * Native event dispatcher usable without any dependency. 19 | * 20 | * This dispatcher simply associates a list of listeners to the only event dispatched by the library. 21 | * Your listeners should use the event payload object to check the event type. 22 | * 23 | * For instance: 24 | * 25 | * ``` php 26 | * $dispatcher = new NativeEventDispatcher(); 27 | * 28 | * $dispatcher->addListener(function($eventName, MessageEvent $event) { 29 | * // You can check $eventName to execute code at some specific events. 30 | * // $event can be an instance of MessageEvent or an instance of ConversationEvent 31 | * // (which extends MessageEvent). 32 | * }); 33 | * 34 | * $sender = new Sender($driver, $dispatcher); 35 | * ``` 36 | * 37 | * @author Titouan Galopin 38 | */ 39 | class NativeEventDispatcher implements EventDispatcherInterface 40 | { 41 | /** 42 | * @var callable[] 43 | */ 44 | private $listeners; 45 | 46 | /** 47 | * Constructor. 48 | */ 49 | public function __construct() 50 | { 51 | $this->listeners = []; 52 | } 53 | 54 | /** 55 | * @param callable $listener 56 | */ 57 | public function addListener($listener) 58 | { 59 | Assert::isCallable( 60 | $listener, 61 | '$listener expected a callable in NativeEventDispatcher::addListener(). Got: %s' 62 | ); 63 | 64 | $this->listeners[] = $listener; 65 | } 66 | 67 | /** 68 | * @param callable $listener 69 | */ 70 | public function removeListener($listener) 71 | { 72 | if ($key = array_search($listener, $this->listeners, true)) { 73 | unset($this->listeners[$key]); 74 | $this->listeners = array_values($this->listeners); 75 | } 76 | } 77 | 78 | /** 79 | * {@inheritdoc} 80 | */ 81 | public function dispatch($eventName, MessageEvent $event) 82 | { 83 | foreach ($this->listeners as $listener) { 84 | $event = call_user_func_array($listener, [$eventName, $event]); 85 | } 86 | 87 | return $event; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/EventDispatcher/SymfonyBridgeEventDispatcher.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\EventDispatcher; 13 | 14 | use FOS\Message\Event\MessageEvent; 15 | use FOS\Message\Event\SymfonyEvent; 16 | use Symfony\Component\EventDispatcher\EventDispatcherInterface as SymfonyEventDispatcherInterface; 17 | 18 | /** 19 | * Event dispatcher to send events to the Symfony event dispatcher. 20 | * 21 | * Simply provides the Symfony dispatcher and this bridge will forward 22 | * the library event to the Symfony event dispatcher. 23 | * 24 | * @author Titouan Galopin 25 | */ 26 | class SymfonyBridgeEventDispatcher implements EventDispatcherInterface 27 | { 28 | /** 29 | * @var SymfonyEventDispatcherInterface 30 | */ 31 | private $symfonyDispatcher; 32 | 33 | /** 34 | * Cosntructor. 35 | * 36 | * @param SymfonyEventDispatcherInterface $symfonyDispatcher 37 | */ 38 | public function __construct(SymfonyEventDispatcherInterface $symfonyDispatcher) 39 | { 40 | $this->symfonyDispatcher = $symfonyDispatcher; 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function dispatch($eventName, MessageEvent $event) 47 | { 48 | return $this->symfonyDispatcher->dispatch($eventName, new SymfonyEvent($event)); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Model/Conversation.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Model; 13 | 14 | use Doctrine\Common\Collections\ArrayCollection; 15 | use Webmozart\Assert\Assert; 16 | 17 | /** 18 | * A conversation is an ordered group of messages with a subject. 19 | * 20 | * @author Titouan Galopin 21 | */ 22 | class Conversation implements ConversationInterface 23 | { 24 | /** 25 | * @var int 26 | */ 27 | protected $id; 28 | 29 | /** 30 | * @var string 31 | */ 32 | protected $subject; 33 | 34 | /** 35 | * @var MessageInterface[]|\Doctrine\Common\Collections\Collection 36 | */ 37 | protected $messages; 38 | 39 | /** 40 | * @var ConversationPersonInterface[]|\Doctrine\Common\Collections\Collection 41 | */ 42 | protected $persons; 43 | 44 | /** 45 | * Constructor. 46 | */ 47 | public function __construct() 48 | { 49 | $this->persons = new ArrayCollection(); 50 | $this->messages = new ArrayCollection(); 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | public function getId() 57 | { 58 | return $this->id; 59 | } 60 | 61 | /** 62 | * {@inheritdoc} 63 | */ 64 | public function getSubject() 65 | { 66 | return $this->subject; 67 | } 68 | 69 | /** 70 | * {@inheritdoc} 71 | */ 72 | public function setSubject($subject) 73 | { 74 | Assert::nullOrString($subject); 75 | 76 | $this->subject = $subject; 77 | } 78 | 79 | /** 80 | * {@inheritdoc} 81 | */ 82 | public function getMessages() 83 | { 84 | return $this->messages; 85 | } 86 | 87 | /** 88 | * {@inheritdoc} 89 | */ 90 | public function getFirstUnreadMessage(PersonInterface $person) 91 | { 92 | foreach ($this->messages as $message) { 93 | if ($message->getReadDate($person) === null) { 94 | return $message; 95 | } 96 | } 97 | 98 | return null; 99 | } 100 | 101 | /** 102 | * {@inheritdoc} 103 | */ 104 | public function addConversationPerson(ConversationPersonInterface $conversationPerson) 105 | { 106 | if (!$this->isPersonInConversation($conversationPerson->getPerson())) { 107 | $this->persons->add($conversationPerson); 108 | } 109 | } 110 | 111 | /** 112 | * {@inheritdoc} 113 | */ 114 | public function removeConversationPerson(ConversationPersonInterface $conversationPerson) 115 | { 116 | $this->persons->removeElement($conversationPerson); 117 | } 118 | 119 | /** 120 | * {@inheritdoc} 121 | */ 122 | public function getConversationPersons() 123 | { 124 | return $this->persons; 125 | } 126 | 127 | /** 128 | * {@inheritdoc} 129 | */ 130 | public function isPersonInConversation(PersonInterface $person) 131 | { 132 | return $this->getConversationPerson($person) instanceof ConversationPersonInterface; 133 | } 134 | 135 | /** 136 | * {@inheritdoc} 137 | */ 138 | public function getConversationPerson(PersonInterface $person) 139 | { 140 | foreach ($this->persons as $conversationPerson) { 141 | if ($conversationPerson->getPerson()->getId() === $person->getId()) { 142 | return $conversationPerson; 143 | } 144 | } 145 | 146 | return null; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/Model/ConversationInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Model; 13 | 14 | use Doctrine\Common\Collections\Collection; 15 | 16 | /** 17 | * A conversation is an ordered group of messages with a subject. 18 | * 19 | * @author Titouan Galopin 20 | * @author Christian Riesen 21 | */ 22 | interface ConversationInterface 23 | { 24 | /** 25 | * Return a unique identifier for this conversation. 26 | * 27 | * @return mixed Unique identifier, can vary depending on system used 28 | */ 29 | public function getId(); 30 | 31 | /** 32 | * Return this conversation subject. 33 | * 34 | * @return string 35 | */ 36 | public function getSubject(); 37 | 38 | /** 39 | * Set this conversation subject. 40 | * 41 | * @param string $subject 42 | */ 43 | public function setSubject($subject); 44 | 45 | /** 46 | * Get all the messages in this conversation. 47 | * Return a list ordered by date ascending. 48 | * 49 | * @return Message[]|Collection 50 | */ 51 | public function getMessages(); 52 | 53 | /** 54 | * Get the first unread message of the conversation by a person, 55 | * or null if all the messages have been read. 56 | * 57 | * @param PersonInterface $person 58 | * 59 | * @return null 60 | */ 61 | public function getFirstUnreadMessage(PersonInterface $person); 62 | 63 | /** 64 | * Add a person to this conversation. 65 | * 66 | * @param ConversationPersonInterface $conversationPerson 67 | */ 68 | public function addConversationPerson(ConversationPersonInterface $conversationPerson); 69 | 70 | /** 71 | * Remove a person from this conversation. 72 | * 73 | * @param ConversationPersonInterface $conversationPerson 74 | */ 75 | public function removeConversationPerson(ConversationPersonInterface $conversationPerson); 76 | 77 | /** 78 | * Get all the persons in this conversation. 79 | * 80 | * @return ConversationPersonInterface[]|\Doctrine\Common\Collections\Collection 81 | */ 82 | public function getConversationPersons(); 83 | 84 | /** 85 | * Return whether the given person is in the conversation or not. 86 | * 87 | * @param PersonInterface $person 88 | * 89 | * @return bool 90 | */ 91 | public function isPersonInConversation(PersonInterface $person); 92 | 93 | /** 94 | * Return the ConversationPerson object associated to the given person 95 | * or null if the person is not a member of this conversation. 96 | * 97 | * @param PersonInterface $person 98 | * 99 | * @return ConversationPerson|null 100 | */ 101 | public function getConversationPerson(PersonInterface $person); 102 | } 103 | -------------------------------------------------------------------------------- /src/Model/ConversationPerson.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Model; 13 | 14 | use Doctrine\Common\Collections\ArrayCollection; 15 | 16 | /** 17 | * A link between a conversation and a person. 18 | * This link is taggable: it accepts tags as a way to describe the state 19 | * of the relation between the person and the conversation of this link. 20 | * For instance, tags could be "deleted", "archived" or "inbox". 21 | * 22 | * @author Titouan Galopin 23 | * @author Christian Riesen 24 | */ 25 | class ConversationPerson implements ConversationPersonInterface 26 | { 27 | /** 28 | * @var ConversationInterface 29 | */ 30 | protected $conversation; 31 | 32 | /** 33 | * @var PersonInterface 34 | */ 35 | protected $person; 36 | 37 | /** 38 | * @var TagInterface[]|\Doctrine\Common\Collections\Collection 39 | */ 40 | protected $tags; 41 | 42 | /** 43 | * Constructor. 44 | * 45 | * @param ConversationInterface $conversation 46 | * @param PersonInterface $person 47 | */ 48 | public function __construct(ConversationInterface $conversation, PersonInterface $person) 49 | { 50 | $this->conversation = $conversation; 51 | $this->person = $person; 52 | $this->tags = new ArrayCollection(); 53 | } 54 | 55 | /** 56 | * @param ConversationInterface $conversation 57 | */ 58 | public function setConversation(ConversationInterface $conversation) 59 | { 60 | $this->conversation = $conversation; 61 | } 62 | 63 | /** 64 | * @param PersonInterface $person 65 | */ 66 | public function setPerson(PersonInterface $person) 67 | { 68 | $this->person = $person; 69 | } 70 | 71 | /** 72 | * {@inheritdoc} 73 | */ 74 | public function getConversation() 75 | { 76 | return $this->conversation; 77 | } 78 | 79 | /** 80 | * {@inheritdoc} 81 | */ 82 | public function getPerson() 83 | { 84 | return $this->person; 85 | } 86 | 87 | /** 88 | * {@inheritdoc} 89 | */ 90 | public function addTag(TagInterface $tag) 91 | { 92 | if ($this->tags->contains($tag)) { 93 | return; 94 | } 95 | 96 | $this->tags->add($tag); 97 | } 98 | 99 | /** 100 | * {@inheritdoc} 101 | */ 102 | public function removeTag(TagInterface $tag) 103 | { 104 | return $this->tags->removeElement($tag); 105 | } 106 | 107 | /** 108 | * {@inheritdoc} 109 | */ 110 | public function hasTag(TagInterface $tag) 111 | { 112 | return $this->tags->contains($tag); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/Model/ConversationPersonInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Model; 13 | 14 | /** 15 | * A link between a conversation and a person. 16 | * This link is taggable: it accepts tags as a way to describe the state 17 | * of the relation between the person and the conversation of this link. 18 | * For instance, tags could be "deleted", "archived" or "inbox". 19 | * 20 | * @author Titouan Galopin 21 | * @author Christian Riesen 22 | */ 23 | interface ConversationPersonInterface 24 | { 25 | /** 26 | * Return the conversation associated to this link. 27 | * 28 | * @return ConversationInterface Conversation associated to this link 29 | */ 30 | public function getConversation(); 31 | 32 | /** 33 | * Return the person associated to this link. 34 | * 35 | * @return PersonInterface Person associated to this link 36 | */ 37 | public function getPerson(); 38 | 39 | /** 40 | * Add the given tag to this entity. 41 | * 42 | * @param TagInterface $tag 43 | */ 44 | public function addTag(TagInterface $tag); 45 | 46 | /** 47 | * Remove the given tag from this entity. 48 | * 49 | * @param TagInterface $tag 50 | */ 51 | public function removeTag(TagInterface $tag); 52 | 53 | /** 54 | * Check if this entity is tagged with the given tag. 55 | * 56 | * @param TagInterface $tag 57 | * 58 | * @return bool 59 | */ 60 | public function hasTag(TagInterface $tag); 61 | } 62 | -------------------------------------------------------------------------------- /src/Model/Message.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Model; 13 | 14 | use Doctrine\Common\Collections\ArrayCollection; 15 | use Webmozart\Assert\Assert; 16 | 17 | /** 18 | * A single message. 19 | * 20 | * @author Titouan Galopin 21 | */ 22 | class Message implements MessageInterface 23 | { 24 | /** 25 | * @var int 26 | */ 27 | protected $id; 28 | 29 | /** 30 | * @var ConversationInterface 31 | */ 32 | protected $conversation; 33 | 34 | /** 35 | * @var PersonInterface 36 | */ 37 | protected $sender; 38 | 39 | /** 40 | * @var string 41 | */ 42 | protected $body; 43 | 44 | /** 45 | * @var \DateTime 46 | */ 47 | protected $date; 48 | 49 | /** 50 | * @var MessagePersonInterface[]|\Doctrine\Common\Collections\Collection 51 | */ 52 | protected $persons; 53 | 54 | /** 55 | * @param ConversationInterface $conversation 56 | * @param PersonInterface $sender 57 | */ 58 | public function __construct(ConversationInterface $conversation, PersonInterface $sender, $body) 59 | { 60 | $this->conversation = $conversation; 61 | $this->sender = $sender; 62 | $this->body = $body; 63 | $this->date = new \DateTime(); 64 | $this->persons = new ArrayCollection(); 65 | } 66 | 67 | /** 68 | * {@inheritdoc} 69 | */ 70 | public function getId() 71 | { 72 | return $this->id; 73 | } 74 | 75 | /** 76 | * {@inheritdoc} 77 | */ 78 | public function getConversation() 79 | { 80 | return $this->conversation; 81 | } 82 | 83 | /** 84 | * {@inheritdoc} 85 | */ 86 | public function getSender() 87 | { 88 | return $this->sender; 89 | } 90 | 91 | /** 92 | * {@inheritdoc} 93 | */ 94 | public function getBody() 95 | { 96 | return $this->body; 97 | } 98 | 99 | /** 100 | * {@inheritdoc} 101 | */ 102 | public function setBody($body) 103 | { 104 | Assert::string($body); 105 | 106 | $this->body = $body; 107 | } 108 | 109 | /** 110 | * {@inheritdoc} 111 | */ 112 | public function getDate() 113 | { 114 | return $this->date; 115 | } 116 | 117 | /** 118 | * {@inheritdoc} 119 | */ 120 | public function setDate(\DateTime $date) 121 | { 122 | $this->date = $date; 123 | } 124 | 125 | /** 126 | * {@inheritdoc} 127 | */ 128 | public function addMessagePerson(MessagePersonInterface $messagePerson) 129 | { 130 | $this->persons->add($messagePerson); 131 | } 132 | 133 | /** 134 | * {@inheritdoc} 135 | */ 136 | public function removeMessagePerson(MessagePersonInterface $messagePerson) 137 | { 138 | $this->persons->removeElement($messagePerson); 139 | } 140 | 141 | /** 142 | * {@inheritdoc} 143 | */ 144 | public function getMessagePersons() 145 | { 146 | return $this->persons; 147 | } 148 | 149 | /** 150 | * {@inheritdoc} 151 | */ 152 | public function getReadDate(PersonInterface $person) 153 | { 154 | $messagePerson = $this->getMessagePerson($person); 155 | 156 | if (! $messagePerson) { 157 | return null; 158 | } 159 | 160 | return $messagePerson->getRead(); 161 | } 162 | 163 | /** 164 | * {@inheritdoc} 165 | */ 166 | public function getMessagePerson(PersonInterface $person) 167 | { 168 | foreach ($this->persons as $messagePerson) { 169 | if ($messagePerson->getPerson()->getId() === $person->getId()) { 170 | return $messagePerson; 171 | } 172 | } 173 | 174 | return null; 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/Model/MessageInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Model; 13 | 14 | /** 15 | * A single message. 16 | * 17 | * @author Titouan Galopin 18 | * @author Christian Riesen 19 | */ 20 | interface MessageInterface 21 | { 22 | /** 23 | * Return a unique identifier for this message. 24 | * 25 | * @return mixed Unique identifier, can vary depending on system used 26 | */ 27 | public function getId(); 28 | 29 | /** 30 | * Return this message conversation. 31 | * 32 | * @return ConversationInterface Conversation this message belongs to 33 | */ 34 | public function getConversation(); 35 | 36 | /** 37 | * Return this message sender. 38 | * 39 | * @return PersonInterface 40 | */ 41 | public function getSender(); 42 | 43 | /** 44 | * Return this message body. 45 | * 46 | * @return string Body of message 47 | */ 48 | public function getBody(); 49 | 50 | /** 51 | * Set the body of this message. 52 | * 53 | * @param string $body 54 | */ 55 | public function setBody($body); 56 | 57 | /** 58 | * Return this message date. 59 | * 60 | * @return \DateTime 61 | */ 62 | public function getDate(); 63 | 64 | /** 65 | * Set the date of this message. 66 | * 67 | * @param \DateTime $body 68 | */ 69 | public function setDate(\DateTime $body); 70 | 71 | /** 72 | * Add a person to this message. 73 | * 74 | * @param MessagePersonInterface $messagePerson 75 | */ 76 | public function addMessagePerson(MessagePersonInterface $messagePerson); 77 | 78 | /** 79 | * Remove a person from this message. 80 | * 81 | * @param MessagePersonInterface $messagePerson 82 | */ 83 | public function removeMessagePerson(MessagePersonInterface $messagePerson); 84 | 85 | /** 86 | * Get all the persons in this message. 87 | * 88 | * @return MessagePersonInterface[]|\Doctrine\Common\Collections\Collection 89 | */ 90 | public function getMessagePersons(); 91 | 92 | /** 93 | * Return the read date of this message by the given person 94 | * or null if the person did not read this message. 95 | * 96 | * @param PersonInterface $person 97 | * 98 | * @return \DateTime|null 99 | */ 100 | public function getReadDate(PersonInterface $person); 101 | 102 | /** 103 | * Return the MessagePerson object associated to the given person 104 | * or null if the person is not a member of the message conversation. 105 | * 106 | * @param PersonInterface $person 107 | * 108 | * @return MessagePerson|null 109 | */ 110 | public function getMessagePerson(PersonInterface $person); 111 | } 112 | -------------------------------------------------------------------------------- /src/Model/MessagePerson.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Model; 13 | 14 | /** 15 | * A link between a message and a person. 16 | * This link store the state of the message (read / unread) 17 | * according to the person. 18 | * 19 | * @author Titouan Galopin 20 | */ 21 | class MessagePerson implements MessagePersonInterface 22 | { 23 | /** 24 | * @var MessageInterface 25 | */ 26 | protected $message; 27 | 28 | /** 29 | * @var PersonInterface 30 | */ 31 | protected $person; 32 | 33 | /** 34 | * @var \DateTime 35 | */ 36 | protected $read; 37 | 38 | /** 39 | * Constructor. 40 | * 41 | * @param MessageInterface $message 42 | * @param PersonInterface $person 43 | */ 44 | public function __construct(MessageInterface $message, PersonInterface $person) 45 | { 46 | $this->message = $message; 47 | $this->person = $person; 48 | $this->read = null; 49 | } 50 | 51 | /** 52 | * {@inheritdoc} 53 | */ 54 | public function getMessage() 55 | { 56 | return $this->message; 57 | } 58 | 59 | /** 60 | * {@inheritdoc} 61 | */ 62 | public function getPerson() 63 | { 64 | return $this->person; 65 | } 66 | 67 | /** 68 | * {@inheritdoc} 69 | */ 70 | public function getRead() 71 | { 72 | return $this->read; 73 | } 74 | 75 | /** 76 | * {@inheritdoc} 77 | */ 78 | public function setRead() 79 | { 80 | $this->read = new \DateTime(); 81 | } 82 | 83 | /** 84 | * {@inheritdoc} 85 | */ 86 | public function setNotRead() 87 | { 88 | $this->read = null; 89 | } 90 | 91 | /** 92 | * {@inheritdoc} 93 | */ 94 | public function isRead() 95 | { 96 | return $this->read instanceof \DateTime; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Model/MessagePersonInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Model; 13 | 14 | /** 15 | * A link between a message and a person. 16 | * This link store the state of the message (read / unread) 17 | * according to the person. 18 | * 19 | * @author Titouan Galopin 20 | * @author Christian Riesen 21 | */ 22 | interface MessagePersonInterface 23 | { 24 | /** 25 | * Return the message associated to this link. 26 | * 27 | * @return MessageInterface Message associated to this link 28 | */ 29 | public function getMessage(); 30 | 31 | /** 32 | * Return the person associated to this link. 33 | * 34 | * @return PersonInterface Person associated to this link 35 | */ 36 | public function getPerson(); 37 | 38 | /** 39 | * Set the read date of the message by the person as now. 40 | */ 41 | public function setRead(); 42 | 43 | /** 44 | * Set the read date of the message as null (unread). 45 | */ 46 | public function setNotRead(); 47 | 48 | /** 49 | * Return the exact time the message was read by the person. 50 | * 51 | * @return \DateTime 52 | */ 53 | public function getRead(); 54 | 55 | /** 56 | * Returns if the message was read or not. 57 | * 58 | * Not strictly needed, just a helper. 59 | * 60 | * @return bool 61 | */ 62 | public function isRead(); 63 | } 64 | -------------------------------------------------------------------------------- /src/Model/PersonInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Model; 13 | 14 | /** 15 | * A person is a user receiving and sending messages. 16 | * You have to implement this interface on your user entity. 17 | * 18 | * @author Titouan Galopin 19 | * @author Christian Riesen 20 | */ 21 | interface PersonInterface 22 | { 23 | /** 24 | * Return a unique identifier for this person. 25 | * 26 | * @return mixed Unique identifier, can vary depending on system used 27 | */ 28 | public function getId(); 29 | } 30 | -------------------------------------------------------------------------------- /src/Model/TagInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Model; 13 | 14 | /** 15 | * A tag is a conversation state for a given user, such as "deleted" or "archived". 16 | * 17 | * @author Titouan Galopin 18 | * @author Christian Riesen 19 | */ 20 | interface TagInterface 21 | { 22 | /** 23 | * Return this tag name. 24 | * 25 | * @return string 26 | */ 27 | public function getName(); 28 | } 29 | -------------------------------------------------------------------------------- /src/Repository.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message; 13 | 14 | use FOS\Message\Driver\DriverInterface; 15 | use FOS\Message\Model\ConversationInterface; 16 | use FOS\Message\Model\PersonInterface; 17 | use Webmozart\Assert\Assert; 18 | 19 | /** 20 | * Fetch conversations, messages and persons in various ways. 21 | * 22 | * @author Titouan Galopin 23 | */ 24 | class Repository implements RepositoryInterface 25 | { 26 | /** 27 | * @var DriverInterface 28 | */ 29 | private $driver; 30 | 31 | /** 32 | * @param DriverInterface $driver 33 | */ 34 | public function __construct(DriverInterface $driver) 35 | { 36 | $this->driver = $driver; 37 | } 38 | 39 | /** 40 | * {@inheritdoc} 41 | */ 42 | public function getPersonConversations(PersonInterface $person, $tag = null) 43 | { 44 | return $this->driver->findPersonConversations($person, $tag); 45 | } 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | public function getConversation($id) 51 | { 52 | return $this->driver->findConversation($id); 53 | } 54 | 55 | /** 56 | * {@inheritdoc} 57 | */ 58 | public function getConversationPerson(ConversationInterface $conversation, PersonInterface $person) 59 | { 60 | return $this->driver->findConversationPerson($conversation, $person); 61 | } 62 | 63 | /** 64 | * {@inheritdoc} 65 | */ 66 | public function countMessages(ConversationInterface $conversation) 67 | { 68 | return $this->driver->countMessages($conversation); 69 | } 70 | 71 | /** 72 | * {@inheritdoc} 73 | */ 74 | public function getMessages(ConversationInterface $conversation, $offset = 0, $limit = 20, $sortDirection = 'ASC') 75 | { 76 | Assert::integer($offset, '$offset expected an integer in Repository::getMessages(). Got: %s'); 77 | Assert::integer($limit, '$limit expected an integer in Repository::getMessages(). Got: %s'); 78 | 79 | Assert::oneOf( 80 | strtoupper($sortDirection), 81 | ['ASC', 'DESC'], 82 | '$sortDirection expected either ASC or DESC in Repository::getMessages(). Got: %s' 83 | ); 84 | 85 | return $this->driver->findMessages($conversation, $offset, $limit, $sortDirection); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/RepositoryInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message; 13 | 14 | use Doctrine\Common\Collections\Collection; 15 | use FOS\Message\Model\ConversationInterface; 16 | use FOS\Message\Model\ConversationPersonInterface; 17 | use FOS\Message\Model\MessageInterface; 18 | use FOS\Message\Model\PersonInterface; 19 | use FOS\Message\Model\TagInterface; 20 | use InvalidArgumentException; 21 | 22 | /** 23 | * Fetch conversations, messages and persons in various ways. 24 | * 25 | * @author Titouan Galopin 26 | * @author Christian Riesen 27 | */ 28 | interface RepositoryInterface 29 | { 30 | /** 31 | * Get conversations of one person 32 | * An optionnal tag can be specified to filter the list. 33 | * 34 | * @param PersonInterface $person 35 | * @param TagInterface|null $tag 36 | * 37 | * @return ConversationInterface[]|Collection A collection of conversations. 38 | */ 39 | public function getPersonConversations(PersonInterface $person, $tag = null); 40 | 41 | /** 42 | * Get single conversation. 43 | * 44 | * @param int $id 45 | * 46 | * @return ConversationInterface 47 | */ 48 | public function getConversation($id); 49 | 50 | /** 51 | * Get a single ConversationPerson entity corresponding to the link 52 | * between the given conversation and the given person or null if no 53 | * such link is found. 54 | * 55 | * @param ConversationInterface $conversation 56 | * @param PersonInterface $person 57 | * 58 | * @return ConversationPersonInterface|null 59 | */ 60 | public function getConversationPerson(ConversationInterface $conversation, PersonInterface $person); 61 | 62 | /** 63 | * Count all the messages of a conversation. 64 | * 65 | * @param ConversationInterface $conversation 66 | * 67 | * @return int The number of messages in the given conversation. 68 | */ 69 | public function countMessages(ConversationInterface $conversation); 70 | 71 | /** 72 | * Get messages of a conversation. 73 | * 74 | * @param ConversationInterface $conversation 75 | * @param int $limit 76 | * @param int $offset 77 | * @param string $sortDirection 78 | * 79 | * @throws InvalidArgumentException If the offset is not an integer. 80 | * @throws InvalidArgumentException If the limit is not an integer. 81 | * @throws InvalidArgumentException If the sort direction is neither ASC nor DESC. 82 | * 83 | * @return MessageInterface[]|Collection A collection of messages. 84 | */ 85 | public function getMessages(ConversationInterface $conversation, $offset = 0, $limit = 20, $sortDirection = 'ASC'); 86 | } 87 | -------------------------------------------------------------------------------- /src/Sender.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message; 13 | 14 | use FOS\Message\Driver\DriverInterface; 15 | use FOS\Message\Event\ConversationEvent; 16 | use FOS\Message\Event\MessageEvent; 17 | use FOS\Message\EventDispatcher\EventDispatcherInterface; 18 | use FOS\Message\Model\ConversationInterface; 19 | use FOS\Message\Model\ConversationPersonInterface; 20 | use FOS\Message\Model\MessageInterface; 21 | use FOS\Message\Model\PersonInterface; 22 | use Webmozart\Assert\Assert; 23 | 24 | /** 25 | * Start conversations and send replies. 26 | * 27 | * @author Titouan Galopin 28 | */ 29 | class Sender implements SenderInterface 30 | { 31 | /** 32 | * @var DriverInterface 33 | */ 34 | private $driver; 35 | 36 | /** 37 | * @var EventDispatcherInterface 38 | */ 39 | private $eventDispatcher; 40 | 41 | /** 42 | * @param DriverInterface $driver 43 | * @param EventDispatcherInterface|null $eventDispatcher 44 | */ 45 | public function __construct(DriverInterface $driver, EventDispatcherInterface $eventDispatcher = null) 46 | { 47 | $this->driver = $driver; 48 | $this->eventDispatcher = $eventDispatcher; 49 | } 50 | 51 | /** 52 | * {@inheritdoc} 53 | */ 54 | public function startConversation(PersonInterface $sender, $recipient, $body, $subject = null) 55 | { 56 | if (!is_array($recipient) && !$recipient instanceof \Traversable) { 57 | $recipient = [$recipient]; 58 | } 59 | 60 | Assert::allIsInstanceOf( 61 | $recipient, 62 | 'FOS\Message\Model\PersonInterface', 63 | '$recipient expected ether an instance or a collection of PersonInterface in Sender::startConversation().' 64 | ); 65 | 66 | Assert::string($body, '$body expected a string in Sender::startConversation(). Got: %s'); 67 | Assert::nullOrString($subject, '$subject expected either a string or null in Sender::startConversation(). Got: %s'); 68 | 69 | // Create conversation and message 70 | $conversation = $this->createAndPersistConversation($subject); 71 | $message = $this->createAndPersistMessage($conversation, $sender, $body); 72 | 73 | // Add the recipients links 74 | foreach ($recipient as $person) { 75 | $this->createAndPersistConversationPerson($conversation, $person); 76 | $this->createAndPersistMessagePerson($message, $person, false); 77 | } 78 | 79 | // Add the sender link 80 | $this->createAndPersistConversationPerson($conversation, $sender); 81 | $this->createAndPersistMessagePerson($message, $sender, true); 82 | 83 | // Dispatch PRE_PERSIST event 84 | $event = new ConversationEvent($conversation, $message); 85 | 86 | $this->dispatchEvent( 87 | EventDispatcherInterface::START_CONVERSATION_PRE_PERSIST, 88 | $event 89 | ); 90 | 91 | $conversation = $event->getConversation(); 92 | $message = $event->getMessage(); 93 | 94 | // Persist 95 | $this->driver->persistConversation($conversation); 96 | $this->driver->persistMessage($message); 97 | $this->driver->flush(); 98 | 99 | // Dispatch POST_PERSIST event 100 | $this->dispatchEvent( 101 | EventDispatcherInterface::START_CONVERSATION_POST_PERSIST, 102 | new ConversationEvent($conversation, $message) 103 | ); 104 | 105 | return $conversation; 106 | } 107 | 108 | /** 109 | * {@inheritdoc} 110 | */ 111 | public function sendMessage(ConversationInterface $conversation, PersonInterface $sender, $body) 112 | { 113 | Assert::string($body, '$body expected a string in Sender::sendMessage(). Got: %s'); 114 | 115 | // Create the message 116 | $message = $this->createAndPersistMessage($conversation, $sender, $body); 117 | 118 | // Add the conversation persons links 119 | foreach ($conversation->getConversationPersons() as $conversationPerson) { 120 | $person = $conversationPerson->getPerson(); 121 | $this->createAndPersistMessagePerson($message, $person, $person->getId() === $sender->getId()); 122 | } 123 | 124 | // Dispatch PRE_PERSIST event 125 | $event = new MessageEvent($message); 126 | 127 | $this->dispatchEvent( 128 | EventDispatcherInterface::SEND_MESSAGE_PRE_PERSIST, 129 | $event 130 | ); 131 | 132 | $message = $event->getMessage(); 133 | 134 | // Persist 135 | $this->driver->persistMessage($message); 136 | $this->driver->flush(); 137 | 138 | // Dispatch POST_PERSIST event 139 | $this->dispatchEvent( 140 | EventDispatcherInterface::SEND_MESSAGE_POST_PERSIST, 141 | new MessageEvent($message) 142 | ); 143 | 144 | return $message; 145 | } 146 | 147 | /** 148 | * Create and persist a conversation object. 149 | * 150 | * @param string $subject 151 | * 152 | * @return ConversationInterface 153 | */ 154 | private function createAndPersistConversation($subject) 155 | { 156 | $conversation = $this->driver->createConversationModel(); 157 | $conversation->setSubject($subject); 158 | 159 | $this->driver->persistConversation($conversation); 160 | 161 | return $conversation; 162 | } 163 | 164 | /** 165 | * Create and persist a conversation person object. 166 | * 167 | * @param ConversationInterface $conversation 168 | * @param PersonInterface $person 169 | * 170 | * @return ConversationPersonInterface 171 | */ 172 | private function createAndPersistConversationPerson(ConversationInterface $conversation, PersonInterface $person) 173 | { 174 | $conversationPerson = $this->driver->createConversationPersonModel($conversation, $person); 175 | 176 | $this->driver->persistConversationPerson($conversationPerson); 177 | 178 | return $conversationPerson; 179 | } 180 | 181 | /** 182 | * Create and persist a message object. 183 | * 184 | * @param ConversationInterface $conversation 185 | * @param PersonInterface $sender 186 | * @param string $body 187 | * 188 | * @return MessageInterface 189 | */ 190 | private function createAndPersistMessage(ConversationInterface $conversation, PersonInterface $sender, $body) 191 | { 192 | $message = $this->driver->createMessageModel($conversation, $sender, $body); 193 | 194 | $this->driver->persistMessage($message); 195 | 196 | return $message; 197 | } 198 | 199 | /** 200 | * Create and persist a message person object. 201 | * 202 | * @param MessageInterface $message 203 | * @param PersonInterface $person 204 | * @param bool $setRead 205 | * 206 | * @return ConversationPersonInterface 207 | */ 208 | private function createAndPersistMessagePerson(MessageInterface $message, PersonInterface $person, $setRead) 209 | { 210 | $messagePerson = $this->driver->createMessagePersonModel($message, $person); 211 | 212 | if ($setRead) { 213 | $messagePerson->setRead(); 214 | } 215 | 216 | $this->driver->persistMessagePerson($messagePerson); 217 | 218 | return $messagePerson; 219 | } 220 | 221 | /** 222 | * @param string $eventName 223 | * @param MessageEvent $event 224 | * 225 | * @return MessageEvent 226 | */ 227 | private function dispatchEvent($eventName, MessageEvent $event) 228 | { 229 | if ($this->eventDispatcher) { 230 | return $this->eventDispatcher->dispatch($eventName, $event); 231 | } 232 | 233 | return $event; 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/SenderInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message; 13 | 14 | use FOS\Message\Model\ConversationInterface; 15 | use FOS\Message\Model\MessageInterface; 16 | use FOS\Message\Model\PersonInterface; 17 | use InvalidArgumentException; 18 | 19 | /** 20 | * Start conversations and send replies. 21 | * 22 | * @author Titouan Galopin 23 | * @author Christian Riesen 24 | */ 25 | interface SenderInterface 26 | { 27 | /** 28 | * @param PersonInterface $sender 29 | * @param array|PersonInterface $recipient One or multiple recepients 30 | * @param string $body 31 | * @param string|null $subject 32 | * 33 | * @throws InvalidArgumentException If the recipient is neither a PersonInterface nor an array of PersonInterface. 34 | * @throws InvalidArgumentException If the body is not a string. 35 | * @throws InvalidArgumentException If the subject is neither null nor a string. 36 | * 37 | * @return ConversationInterface 38 | */ 39 | public function startConversation(PersonInterface $sender, $recipient, $body, $subject = null); 40 | 41 | /** 42 | * @param ConversationInterface $conversation 43 | * @param PersonInterface $sender 44 | * @param string $body 45 | * 46 | * @throws InvalidArgumentException If the body is not a string. 47 | * 48 | * @return MessageInterface 49 | */ 50 | public function sendMessage(ConversationInterface $conversation, PersonInterface $sender, $body); 51 | } 52 | -------------------------------------------------------------------------------- /src/Tagger.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message; 13 | 14 | use FOS\Message\Driver\DriverInterface; 15 | use FOS\Message\Model\ConversationInterface; 16 | use FOS\Message\Model\ConversationPersonInterface; 17 | use FOS\Message\Model\PersonInterface; 18 | use FOS\Message\Model\TagInterface; 19 | 20 | /** 21 | * Add and remove tags associated to conversation by persons. 22 | * A tag is a simple marker used to filter conversations afterwards 23 | * (such as "archived", "deleted", etc.). 24 | * 25 | * @author Titouan Galopin 26 | */ 27 | class Tagger implements TaggerInterface 28 | { 29 | /** 30 | * @var DriverInterface 31 | */ 32 | private $driver; 33 | 34 | /** 35 | * @var RepositoryInterface 36 | */ 37 | private $repository; 38 | 39 | /** 40 | * @param DriverInterface $driver 41 | * @param RepositoryInterface $repository 42 | */ 43 | public function __construct(DriverInterface $driver, RepositoryInterface $repository) 44 | { 45 | $this->driver = $driver; 46 | $this->repository = $repository; 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | public function addTag(ConversationInterface $conversation, PersonInterface $person, TagInterface $tag) 53 | { 54 | $conversationPerson = $this->repository->getConversationPerson($conversation, $person); 55 | 56 | if (!$conversationPerson instanceof ConversationPersonInterface) { 57 | throw new \LogicException(sprintf( 58 | 'Link between conversation %s and person %s was not found in %s', 59 | $conversation->getId(), $person->getId(), __METHOD__ 60 | )); 61 | } 62 | 63 | if ($conversationPerson->hasTag($tag)) { 64 | return false; 65 | } 66 | 67 | $conversationPerson->addTag($tag); 68 | 69 | $this->driver->persistConversationPerson($conversationPerson); 70 | $this->driver->flush(); 71 | 72 | return true; 73 | } 74 | 75 | /** 76 | * {@inheritdoc} 77 | */ 78 | public function hasTag(ConversationInterface $conversation, PersonInterface $person, TagInterface $tag) 79 | { 80 | $conversationPerson = $this->repository->getConversationPerson($conversation, $person); 81 | 82 | if (!$conversationPerson instanceof ConversationPersonInterface) { 83 | return false; 84 | } 85 | 86 | return $conversationPerson->hasTag($tag); 87 | } 88 | 89 | /** 90 | * {@inheritdoc} 91 | */ 92 | public function removeTag(ConversationInterface $conversation, PersonInterface $person, TagInterface $tag) 93 | { 94 | $conversationPerson = $this->repository->getConversationPerson($conversation, $person); 95 | 96 | if (!$conversationPerson instanceof ConversationPersonInterface) { 97 | throw new \LogicException(sprintf( 98 | 'Link between conversation %s and person %s was not found in %s', 99 | $conversation->getId(), $person->getId(), __METHOD__ 100 | )); 101 | } 102 | 103 | if (!$conversationPerson->hasTag($tag)) { 104 | return false; 105 | } 106 | 107 | $conversationPerson->removeTag($tag); 108 | 109 | $this->driver->persistConversationPerson($conversationPerson); 110 | $this->driver->flush(); 111 | 112 | return true; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/TaggerInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message; 13 | 14 | use FOS\Message\Model\ConversationInterface; 15 | use FOS\Message\Model\PersonInterface; 16 | use FOS\Message\Model\TagInterface; 17 | 18 | /** 19 | * Add and remove tags associated to conversation by persons 20 | * A tag is a simple marker used to filter conversations afterwards 21 | * (such as "inbox", "archived", "read", etc.). 22 | * 23 | * @author Titouan Galopin 24 | * @author Christian Riesen 25 | */ 26 | interface TaggerInterface 27 | { 28 | /** 29 | * Add a tag on the given conversation for the given person 30 | * This tag aims to be used as a filter afterwards. 31 | * 32 | * @param ConversationInterface $conversation 33 | * @param PersonInterface $person 34 | * @param TagInterface $tag 35 | * 36 | * @return bool Whether the tag has been added successfully or not 37 | */ 38 | public function addTag(ConversationInterface $conversation, PersonInterface $person, TagInterface $tag); 39 | 40 | /** 41 | * Check if the given conversation has a tag for the given person. 42 | * 43 | * @param ConversationInterface $conversation 44 | * @param PersonInterface $person 45 | * @param TagInterface $tag 46 | * 47 | * @return bool 48 | */ 49 | public function hasTag(ConversationInterface $conversation, PersonInterface $person, TagInterface $tag); 50 | 51 | /** 52 | * Remove a tag from the given conversation for the given person. 53 | * 54 | * @param ConversationInterface $conversation 55 | * @param PersonInterface $person 56 | * @param TagInterface $tag 57 | * 58 | * @return bool Whether the tag has been removed successfully or not 59 | */ 60 | public function removeTag(ConversationInterface $conversation, PersonInterface $person, TagInterface $tag); 61 | } 62 | -------------------------------------------------------------------------------- /tests/Driver/AbstractDriverTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Tests\Driver; 13 | 14 | use FOS\Message\Driver\DriverInterface; 15 | use FOS\Message\Model\MessageInterface; 16 | use FOS\Message\Model\PersonInterface; 17 | use PHPUnit_Framework_TestCase; 18 | 19 | abstract class AbstractDriverTest extends PHPUnit_Framework_TestCase 20 | { 21 | /** 22 | * @var DriverInterface 23 | */ 24 | protected $driver; 25 | 26 | /** 27 | * @return DriverInterface 28 | */ 29 | abstract protected function createDriver(); 30 | 31 | /** 32 | * @return PersonInterface 33 | */ 34 | abstract protected function createPerson(); 35 | 36 | /** 37 | * Set up the driver. 38 | */ 39 | public function setUp() 40 | { 41 | $this->driver = $this->createDriver(); 42 | } 43 | 44 | public function testFindConversation() 45 | { 46 | $conversation = $this->driver->createConversationModel(); 47 | $conversation->setSubject('Subject'); 48 | $this->driver->persistConversation($conversation); 49 | 50 | $this->driver->flush(); 51 | 52 | $fetched = $this->driver->findConversation($conversation->getId()); 53 | 54 | $this->assertInstanceOf(get_class($conversation), $fetched); 55 | $this->assertEquals($conversation->getSubject(), $fetched->getSubject()); 56 | } 57 | 58 | public function testFindPersonConversations() 59 | { 60 | $person = $this->createPerson(); 61 | 62 | $conversation = $this->driver->createConversationModel(); 63 | $conversation->setSubject('Subject'); 64 | $this->driver->persistConversation($conversation); 65 | 66 | $personModel = $this->driver->createConversationPersonModel($conversation, $person); 67 | $this->driver->persistConversationPerson($personModel); 68 | 69 | $this->driver->flush(); 70 | 71 | $fetched = $this->driver->findPersonConversations($person); 72 | 73 | $this->assertInstanceOf('Doctrine\Common\Collections\Collection', $fetched); 74 | $this->assertCount(1, $fetched); 75 | $this->assertInstanceOf(get_class($conversation), $fetched[0]); 76 | $this->assertEquals($conversation->getSubject(), $fetched[0]->getSubject()); 77 | } 78 | 79 | public function testFindConversationPerson() 80 | { 81 | $person = $this->createPerson(); 82 | 83 | $conversation = $this->driver->createConversationModel(); 84 | $conversation->setSubject('Subject'); 85 | $this->driver->persistConversation($conversation); 86 | 87 | $personModel = $this->driver->createConversationPersonModel($conversation, $person); 88 | $this->driver->persistConversationPerson($personModel); 89 | 90 | $this->driver->flush(); 91 | 92 | $fetched = $this->driver->findConversationPerson($conversation, $person); 93 | 94 | $this->assertInstanceOf(get_class($personModel), $fetched); 95 | $this->assertEquals($personModel->getConversation(), $conversation); 96 | $this->assertEquals($personModel->getPerson(), $person); 97 | } 98 | 99 | public function testFindMessagesAsc() 100 | { 101 | $conversation = $this->driver->createConversationModel(); 102 | $conversation->setSubject('Subject'); 103 | $this->driver->persistConversation($conversation); 104 | 105 | $firstMessage = $this->driver->createMessageModel($conversation, $this->createPerson(), 'Body1'); 106 | $this->driver->persistMessage($firstMessage); 107 | 108 | $secondMessage = $this->driver->createMessageModel($conversation, $this->createPerson(), 'Body2'); 109 | $this->driver->persistMessage($secondMessage); 110 | 111 | $this->driver->flush(); 112 | 113 | $conversation = $this->driver->findConversation($conversation->getId()); 114 | $fetched = $this->driver->findMessages($conversation, 0, 20, 'ASC'); 115 | 116 | $this->assertInstanceOf('Doctrine\Common\Collections\Collection', $fetched); 117 | $this->assertCount(2, $fetched); 118 | $this->assertMessagesEquals($firstMessage, $fetched[0]); 119 | $this->assertMessagesEquals($secondMessage, $fetched[1]); 120 | 121 | foreach ($conversation->getMessages() as $key => $message) { 122 | $this->assertMessagesEquals($message, $fetched[$key]); 123 | } 124 | } 125 | 126 | public function testFindMessagesDesc() 127 | { 128 | $conversation = $this->driver->createConversationModel(); 129 | $conversation->setSubject('Subject'); 130 | $this->driver->persistConversation($conversation); 131 | 132 | $firstMessage = $this->driver->createMessageModel($conversation, $this->createPerson(), 'Body1'); 133 | $this->driver->persistMessage($firstMessage); 134 | 135 | $secondMessage = $this->driver->createMessageModel($conversation, $this->createPerson(), 'Body2'); 136 | $this->driver->persistMessage($secondMessage); 137 | 138 | $this->driver->flush(); 139 | 140 | $fetched = $this->driver->findMessages($conversation, 0, 20, 'DESC'); 141 | 142 | $this->assertInstanceOf('Doctrine\Common\Collections\Collection', $fetched); 143 | $this->assertCount(2, $fetched); 144 | $this->assertMessagesEquals($secondMessage, $fetched[0]); 145 | $this->assertMessagesEquals($firstMessage, $fetched[1]); 146 | } 147 | 148 | public function testFindMessagesLimit() 149 | { 150 | $conversation = $this->driver->createConversationModel(); 151 | $conversation->setSubject('Subject'); 152 | $this->driver->persistConversation($conversation); 153 | 154 | $firstMessage = $this->driver->createMessageModel($conversation, $this->createPerson(), 'Body1'); 155 | $this->driver->persistMessage($firstMessage); 156 | 157 | $secondMessage = $this->driver->createMessageModel($conversation, $this->createPerson(), 'Body2'); 158 | $this->driver->persistMessage($secondMessage); 159 | 160 | $thirdMessage = $this->driver->createMessageModel($conversation, $this->createPerson(), 'Body3'); 161 | $this->driver->persistMessage($thirdMessage); 162 | 163 | $this->driver->flush(); 164 | 165 | $fetched = $this->driver->findMessages($conversation, 0, 2, 'ASC'); 166 | 167 | $this->assertInstanceOf('Doctrine\Common\Collections\Collection', $fetched); 168 | $this->assertCount(2, $fetched); 169 | $this->assertMessagesEquals($firstMessage, $fetched[0]); 170 | $this->assertMessagesEquals($secondMessage, $fetched[1]); 171 | 172 | $fetched = $this->driver->findMessages($conversation, 1, 2, 'ASC'); 173 | 174 | $this->assertInstanceOf('Doctrine\Common\Collections\Collection', $fetched); 175 | $this->assertCount(2, $fetched); 176 | $this->assertMessagesEquals($secondMessage, $fetched[0]); 177 | $this->assertMessagesEquals($thirdMessage, $fetched[1]); 178 | } 179 | 180 | /** 181 | * @param MessageInterface $excepted 182 | * @param MessageInterface $actual 183 | */ 184 | private function assertMessagesEquals($excepted, $actual) 185 | { 186 | $this->assertInstanceOf(get_class($excepted), $actual); 187 | $this->assertEquals($excepted->getBody(), $actual->getBody()); 188 | $this->assertEquals($excepted->getConversation()->getId(), $actual->getConversation()->getId()); 189 | $this->assertEquals($excepted->getSender()->getId(), $actual->getSender()->getId()); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /tests/Driver/Doctrine/ORM/DoctrineORMDriverTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Tests\Driver\Doctrine\ORM; 13 | 14 | use Doctrine\Common\EventManager; 15 | use Doctrine\ORM\EntityManager; 16 | use Doctrine\ORM\Events; 17 | use Doctrine\ORM\Tools\ResolveTargetEntityListener; 18 | use FOS\Message\Driver\Doctrine\ORM\DoctrineORMDriver; 19 | use FOS\Message\Tests\Driver\AbstractDriverTest; 20 | use FOS\Message\Tests\Driver\Doctrine\ORM\Entity\TestPerson; 21 | 22 | class DoctrineORMDriverTest extends AbstractDriverTest 23 | { 24 | /** 25 | * @var EntityManager 26 | */ 27 | private $entityManager; 28 | 29 | /** 30 | * @var int 31 | */ 32 | private static $createdUsers = 0; 33 | 34 | /** 35 | * @var string 36 | */ 37 | private static $dbFile; 38 | 39 | /** 40 | * Build the SQLite database before the tests suite. 41 | */ 42 | public static function setUpBeforeClass() 43 | { 44 | self::$dbFile = sys_get_temp_dir().'/fos-message/doctrine-orm.db'; 45 | 46 | if (file_exists(self::$dbFile)) { 47 | unlink(self::$dbFile); 48 | } elseif (!file_exists(dirname(self::$dbFile))) { 49 | mkdir(dirname(self::$dbFile), 0777, true); 50 | } 51 | 52 | $em = self::createEntityManager(); 53 | 54 | $tool = new \Doctrine\ORM\Tools\SchemaTool($em); 55 | 56 | $tool->updateSchema([ 57 | $em->getClassMetadata('FOS\Message\Driver\Doctrine\ORM\Entity\Conversation'), 58 | $em->getClassMetadata('FOS\Message\Driver\Doctrine\ORM\Entity\ConversationPerson'), 59 | $em->getClassMetadata('FOS\Message\Driver\Doctrine\ORM\Entity\Message'), 60 | $em->getClassMetadata('FOS\Message\Driver\Doctrine\ORM\Entity\MessagePerson'), 61 | $em->getClassMetadata('FOS\Message\Driver\Doctrine\ORM\Entity\Tag'), 62 | $em->getClassMetadata('FOS\Message\Tests\Driver\Doctrine\ORM\Entity\TestPerson'), 63 | ]); 64 | } 65 | 66 | /** 67 | * Remove the SQLite database after the tests suite. 68 | */ 69 | public static function tearDownAfterClass() 70 | { 71 | if (file_exists(self::$dbFile)) { 72 | unlink(self::$dbFile); 73 | } 74 | } 75 | 76 | protected function createDriver() 77 | { 78 | $this->entityManager = self::createEntityManager(); 79 | 80 | return new DoctrineORMDriver($this->entityManager); 81 | } 82 | 83 | /** 84 | * @return TestPerson 85 | */ 86 | protected function createPerson() 87 | { 88 | ++self::$createdUsers; 89 | 90 | $user = new TestPerson(); 91 | $user->setLogin('user'.self::$createdUsers); 92 | 93 | $this->entityManager->persist($user); 94 | $this->entityManager->flush(); 95 | 96 | return $user; 97 | } 98 | 99 | /** 100 | * @return EntityManager 101 | */ 102 | private static function createEntityManager() 103 | { 104 | $config = \Doctrine\ORM\Tools\Setup::createConfiguration(true); 105 | 106 | $config->setMetadataDriverImpl($config->newDefaultAnnotationDriver([ 107 | __DIR__.'/../../../../src/Driver/Doctrine/ORM/Entity', 108 | __DIR__.'/Entity', 109 | ], false)); 110 | 111 | $rtel = new ResolveTargetEntityListener(); 112 | $rtel->addResolveTargetEntity( 113 | 'FOS\Message\Model\PersonInterface', 114 | 'FOS\Message\Tests\Driver\Doctrine\ORM\Entity\TestPerson', 115 | [] 116 | ); 117 | 118 | $evm = new EventManager(); 119 | $evm->addEventListener(Events::loadClassMetadata, $rtel); 120 | 121 | $dbParams = [ 122 | 'driver' => 'pdo_sqlite', 123 | 'path' => self::$dbFile, 124 | ]; 125 | 126 | return \Doctrine\ORM\EntityManager::create($dbParams, $config, $evm); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /tests/Driver/Doctrine/ORM/Entity/TestPerson.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Tests\Driver\Doctrine\ORM\Entity; 13 | 14 | use Doctrine\ORM\Mapping as ORM; 15 | use FOS\Message\Model\PersonInterface; 16 | 17 | /** 18 | * @ORM\Table(name="persons") 19 | * @ORM\Entity 20 | */ 21 | class TestPerson implements PersonInterface 22 | { 23 | /** 24 | * @ORM\Id 25 | * @ORM\Column(type="integer") 26 | * @ORM\GeneratedValue(strategy="AUTO") 27 | */ 28 | protected $id; 29 | 30 | /** 31 | * @ORM\Column(type="string", length=100) 32 | */ 33 | protected $login; 34 | 35 | /** 36 | * @return mixed 37 | */ 38 | public function getId() 39 | { 40 | return $this->id; 41 | } 42 | 43 | /** 44 | * @return mixed 45 | */ 46 | public function getLogin() 47 | { 48 | return $this->login; 49 | } 50 | 51 | /** 52 | * @param mixed $login 53 | */ 54 | public function setLogin($login) 55 | { 56 | $this->login = $login; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/EventDispatcher/NativeEventDispatcherTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Tests\EventDispatcher; 13 | 14 | use FOS\Message\Event\ConversationEvent; 15 | use FOS\Message\Event\MessageEvent; 16 | use FOS\Message\EventDispatcher\EventDispatcherInterface; 17 | use FOS\Message\EventDispatcher\NativeEventDispatcher; 18 | use FOS\Message\Model\Conversation; 19 | use FOS\Message\Model\Message; 20 | use Mockery; 21 | use PHPUnit_Framework_TestCase; 22 | 23 | class NativeEventDispatcherTest extends PHPUnit_Framework_TestCase 24 | { 25 | /** 26 | * @var NativeEventDispatcher 27 | */ 28 | private $dispatcher; 29 | 30 | public function setUp() 31 | { 32 | $this->dispatcher = new NativeEventDispatcher(); 33 | } 34 | 35 | /** 36 | * @dataProvider getCallbacks 37 | */ 38 | public function testMessageListener($eventName, $listener) 39 | { 40 | $this->dispatcher->addListener($listener); 41 | 42 | $person = Mockery::mock('FOS\Message\Model\PersonInterface'); 43 | $message = new Message(new Conversation(), $person, 'BodyUnchanged'); 44 | 45 | $this->assertEquals('BodyUnchanged', $message->getBody()); 46 | 47 | $event = $this->dispatcher->dispatch($eventName, new MessageEvent($message)); 48 | 49 | $this->assertEquals('BodyEdited', $message->getBody()); 50 | $this->assertEquals('BodyEdited', $event->getMessage()->getBody()); 51 | } 52 | 53 | /** 54 | * @dataProvider getCallbacks 55 | */ 56 | public function testConversationListener($eventName, $listener) 57 | { 58 | $this->dispatcher->addListener($listener); 59 | 60 | $conversation = new Conversation(); 61 | $conversation->setSubject('SubjectUnchanged'); 62 | 63 | $person = Mockery::mock('FOS\Message\Model\PersonInterface'); 64 | $message = new Message($conversation, $person, 'BodyUnchanged'); 65 | 66 | $this->assertEquals('SubjectUnchanged', $conversation->getSubject()); 67 | $this->assertEquals('BodyUnchanged', $message->getBody()); 68 | 69 | $event = $this->dispatcher->dispatch($eventName, new ConversationEvent($conversation, $message)); 70 | 71 | $this->assertEquals('SubjectEdited', $conversation->getSubject()); 72 | $this->assertEquals('SubjectEdited', $event->getConversation()->getSubject()); 73 | $this->assertEquals('BodyEdited', $message->getBody()); 74 | $this->assertEquals('BodyEdited', $event->getMessage()->getBody()); 75 | } 76 | 77 | public function getCallbacks() 78 | { 79 | return [ 80 | 'Array callable START_CONVERSATION_PRE_PERSIST' => [ 81 | EventDispatcherInterface::START_CONVERSATION_PRE_PERSIST, 82 | [$this, 'callbackListener'], 83 | ], 84 | 'Array callable START_CONVERSATION_POST_PERSIST' => [ 85 | EventDispatcherInterface::START_CONVERSATION_POST_PERSIST, 86 | [$this, 'callbackListener'], 87 | ], 88 | 'Anonymous function START_CONVERSATION_PRE_PERSIST' => [ 89 | EventDispatcherInterface::START_CONVERSATION_PRE_PERSIST, 90 | function ($eventName, MessageEvent $event) { 91 | if ($event instanceof ConversationEvent) { 92 | $event->getConversation()->setSubject('SubjectEdited'); 93 | } 94 | 95 | $event->getMessage()->setBody('BodyEdited'); 96 | 97 | return $event; 98 | }, 99 | ], 100 | 'Anonymous function START_CONVERSATION_POST_PERSIST' => [ 101 | EventDispatcherInterface::START_CONVERSATION_POST_PERSIST, 102 | function ($eventName, MessageEvent $event) { 103 | if ($event instanceof ConversationEvent) { 104 | $event->getConversation()->setSubject('SubjectEdited'); 105 | } 106 | 107 | $event->getMessage()->setBody('BodyEdited'); 108 | 109 | return $event; 110 | }, 111 | ], 112 | 'Array callable SEND_MESSAGE_PRE_PERSIST' => [ 113 | EventDispatcherInterface::SEND_MESSAGE_PRE_PERSIST, 114 | [$this, 'callbackListener'], 115 | ], 116 | 'Array callable SEND_MESSAGE_POST_PERSIST' => [ 117 | EventDispatcherInterface::SEND_MESSAGE_POST_PERSIST, 118 | [$this, 'callbackListener'], 119 | ], 120 | 'Anonymous function SEND_MESSAGE_PRE_PERSIST' => [ 121 | EventDispatcherInterface::SEND_MESSAGE_PRE_PERSIST, 122 | function ($eventName, MessageEvent $event) { 123 | if ($event instanceof ConversationEvent) { 124 | $event->getConversation()->setSubject('SubjectEdited'); 125 | } 126 | 127 | $event->getMessage()->setBody('BodyEdited'); 128 | 129 | return $event; 130 | }, 131 | ], 132 | 'Anonymous function SEND_MESSAGE_POST_PERSIST' => [ 133 | EventDispatcherInterface::SEND_MESSAGE_POST_PERSIST, 134 | function ($eventName, MessageEvent $event) { 135 | if ($event instanceof ConversationEvent) { 136 | $event->getConversation()->setSubject('SubjectEdited'); 137 | } 138 | 139 | $event->getMessage()->setBody('BodyEdited'); 140 | 141 | return $event; 142 | }, 143 | ], 144 | ]; 145 | } 146 | 147 | public function callbackListener($eventName, MessageEvent $event) 148 | { 149 | if ($event instanceof ConversationEvent) { 150 | $event->getConversation()->setSubject('SubjectEdited'); 151 | } 152 | 153 | $event->getMessage()->setBody('BodyEdited'); 154 | 155 | return $event; 156 | } 157 | 158 | public function testMultipleListeners() 159 | { 160 | $this->dispatcher->addListener(function ($eventName, MessageEvent $event) { 161 | $event->getMessage()->setBody('BodyEditedFirst'); 162 | 163 | return $event; 164 | }); 165 | 166 | $this->dispatcher->addListener(function ($eventName, MessageEvent $event) { 167 | $event->getMessage()->setBody('BodyEditedSecond'); 168 | 169 | return $event; 170 | }); 171 | 172 | $person = Mockery::mock('FOS\Message\Model\PersonInterface'); 173 | $message = new Message(new Conversation(), $person, 'BodyUnchanged'); 174 | 175 | $this->assertEquals('BodyUnchanged', $message->getBody()); 176 | 177 | $event = $this->dispatcher->dispatch( 178 | EventDispatcherInterface::SEND_MESSAGE_PRE_PERSIST, 179 | new MessageEvent($message) 180 | ); 181 | 182 | $this->assertEquals('BodyEditedSecond', $message->getBody()); 183 | $this->assertEquals('BodyEditedSecond', $event->getMessage()->getBody()); 184 | } 185 | 186 | public function testRemoveListener() 187 | { 188 | $this->dispatcher->addListener(function ($eventName, MessageEvent $event) { 189 | $event->getMessage()->setBody('BodyEditedFirst'); 190 | 191 | return $event; 192 | }); 193 | 194 | $secondListener = function ($eventName, MessageEvent $event) { 195 | $event->getMessage()->setBody('BodyEditedSecond'); 196 | 197 | return $event; 198 | }; 199 | 200 | $this->dispatcher->addListener($secondListener); 201 | $this->dispatcher->removeListener($secondListener); 202 | 203 | $person = Mockery::mock('FOS\Message\Model\PersonInterface'); 204 | $message = new Message(new Conversation(), $person, 'BodyUnchanged'); 205 | 206 | $this->assertEquals('BodyUnchanged', $message->getBody()); 207 | 208 | $event = $this->dispatcher->dispatch( 209 | EventDispatcherInterface::SEND_MESSAGE_PRE_PERSIST, 210 | new MessageEvent($message) 211 | ); 212 | 213 | $this->assertEquals('BodyEditedFirst', $message->getBody()); 214 | $this->assertEquals('BodyEditedFirst', $event->getMessage()->getBody()); 215 | } 216 | 217 | /** 218 | * @expectedException \InvalidArgumentException 219 | */ 220 | public function testAddInvalidListener() 221 | { 222 | $this->dispatcher->addListener(new \stdClass()); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /tests/RepositoryTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Tests; 13 | 14 | use FOS\Message\Driver\DriverInterface; 15 | use FOS\Message\Repository; 16 | use Mockery; 17 | use Mockery\MockInterface; 18 | use PHPUnit_Framework_TestCase; 19 | 20 | class RepositoryTest extends PHPUnit_Framework_TestCase 21 | { 22 | /** 23 | * @var DriverInterface|MockInterface 24 | */ 25 | private $driver; 26 | 27 | /** 28 | * @var Repository 29 | */ 30 | private $repository; 31 | 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | protected function setUp() 36 | { 37 | $this->driver = Mockery::mock('FOS\Message\Driver\DriverInterface'); 38 | $this->repository = new Repository($this->driver); 39 | } 40 | 41 | public function testGetPersonConversations() 42 | { 43 | $user = Mockery::mock('FOS\Message\Model\PersonInterface'); 44 | $tag = Mockery::mock('FOS\Message\Model\TagInterface'); 45 | $conversation = Mockery::mock('FOS\Message\Model\ConversationInterface'); 46 | 47 | $this->driver->shouldReceive('findPersonConversations') 48 | ->once() 49 | ->with($user, $tag) 50 | ->andReturn([$conversation]); 51 | 52 | $this->assertSame([$conversation], $this->repository->getPersonConversations($user, $tag)); 53 | } 54 | 55 | public function testGetConversation() 56 | { 57 | $conversation = Mockery::mock('FOS\Message\Model\ConversationInterface'); 58 | 59 | $this->driver->shouldReceive('findConversation') 60 | ->once() 61 | ->with(4) 62 | ->andReturn($conversation); 63 | 64 | $this->assertSame($conversation, $this->repository->getConversation(4)); 65 | } 66 | 67 | public function testGetConversationPersons() 68 | { 69 | $conversation = Mockery::mock('FOS\Message\Model\ConversationInterface'); 70 | $user = Mockery::mock('FOS\Message\Model\PersonInterface'); 71 | $conversationPerson = Mockery::mock('FOS\Message\Model\ConversationPersonInterface'); 72 | 73 | $this->driver->shouldReceive('findConversationPerson') 74 | ->once() 75 | ->with($conversation, $user) 76 | ->andReturn($conversationPerson); 77 | 78 | $this->assertSame($conversationPerson, $this->repository->getConversationPerson($conversation, $user)); 79 | } 80 | 81 | public function testCountMessages() 82 | { 83 | $conversation = Mockery::mock('FOS\Message\Model\ConversationInterface'); 84 | 85 | $this->driver->shouldReceive('countMessages') 86 | ->once() 87 | ->with($conversation) 88 | ->andReturn(4); 89 | 90 | $this->assertSame(4, $this->repository->countMessages($conversation)); 91 | } 92 | 93 | public function testGetMessages() 94 | { 95 | $conversation = Mockery::mock('FOS\Message\Model\ConversationInterface'); 96 | $message = Mockery::mock('FOS\Message\Model\PersonInterface'); 97 | 98 | $this->driver->shouldReceive('findMessages') 99 | ->once() 100 | ->with($conversation, 5, 10, 'DESC') 101 | ->andReturn([$message]); 102 | 103 | $this->assertSame([$message], $this->repository->getMessages($conversation, 5, 10, 'DESC')); 104 | } 105 | 106 | /** 107 | * @expectedException \InvalidArgumentException 108 | */ 109 | public function testGetMessagesInvalidOffset() 110 | { 111 | $conversation = Mockery::mock('FOS\Message\Model\ConversationInterface'); 112 | $this->repository->getMessages($conversation, 'invalid', 10, 'DESC'); 113 | } 114 | 115 | /** 116 | * @expectedException \InvalidArgumentException 117 | */ 118 | public function testGetMessagesInvalidLimit() 119 | { 120 | $conversation = Mockery::mock('FOS\Message\Model\ConversationInterface'); 121 | $this->repository->getMessages($conversation, 0, 'invalid', 'DESC'); 122 | } 123 | 124 | /** 125 | * @expectedException \InvalidArgumentException 126 | */ 127 | public function testGetMessagesInvalidSortDirection() 128 | { 129 | $conversation = Mockery::mock('FOS\Message\Model\ConversationInterface'); 130 | $this->repository->getMessages($conversation, 0, 10, 'invalid'); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /tests/SenderTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Tests; 13 | 14 | use FOS\Message\Driver\DriverInterface; 15 | use FOS\Message\EventDispatcher\EventDispatcherInterface; 16 | use FOS\Message\Sender; 17 | use Mockery; 18 | use Mockery\MockInterface; 19 | use PHPUnit_Framework_TestCase; 20 | 21 | class SenderTest extends PHPUnit_Framework_TestCase 22 | { 23 | /** 24 | * @var DriverInterface|MockInterface 25 | */ 26 | private $driver; 27 | 28 | /** 29 | * @var EventDispatcherInterface|MockInterface 30 | */ 31 | private $dispatcher; 32 | 33 | /** 34 | * @var Sender 35 | */ 36 | private $sender; 37 | 38 | /** 39 | * {@inheritdoc} 40 | */ 41 | protected function setUp() 42 | { 43 | $this->driver = Mockery::mock('FOS\Message\Driver\DriverInterface'); 44 | $this->dispatcher = Mockery::mock('FOS\Message\EventDispatcher\EventDispatcherInterface'); 45 | $this->sender = new Sender($this->driver, $this->dispatcher); 46 | } 47 | 48 | public function testStartConversation() 49 | { 50 | $from = Mockery::mock('FOS\Message\Model\PersonInterface'); 51 | $firstRecipient = Mockery::mock('FOS\Message\Model\PersonInterface'); 52 | $secondRecipient = Mockery::mock('FOS\Message\Model\PersonInterface'); 53 | 54 | $conversation = Mockery::mock('FOS\Message\Model\ConversationInterface'); 55 | $conversation->shouldReceive('setSubject')->with('Subject')->andReturnSelf(); 56 | 57 | $message = Mockery::mock('FOS\Message\Model\MessageInterface'); 58 | 59 | $fromConversationModel = Mockery::mock('FOS\Message\Model\ConversationPersonInterface'); 60 | $firstConversationModel = Mockery::mock('FOS\Message\Model\ConversationPersonInterface'); 61 | $secondConversationModel = Mockery::mock('FOS\Message\Model\ConversationPersonInterface'); 62 | 63 | $fromMessageModel = Mockery::mock('FOS\Message\Model\MessagePersonInterface'); 64 | $fromMessageModel->shouldReceive('setRead')->withNoArgs()->andReturnSelf(); 65 | 66 | $firstMessageModel = Mockery::mock('FOS\Message\Model\MessagePersonInterface'); 67 | $secondMessageModel = Mockery::mock('FOS\Message\Model\MessagePersonInterface'); 68 | 69 | // Create the conversation 70 | $this->driver->shouldReceive('createConversationModel') 71 | ->once() 72 | ->withNoArgs() 73 | ->andReturn($conversation); 74 | 75 | $this->driver->shouldReceive('persistConversation') 76 | ->once() 77 | ->with($conversation) 78 | ->andReturn(true); 79 | 80 | // Create the first message 81 | $this->driver->shouldReceive('createMessageModel') 82 | ->once() 83 | ->with($conversation, $from, 'Body') 84 | ->andReturn($message); 85 | 86 | $this->driver->shouldReceive('persistMessage') 87 | ->once() 88 | ->with($message) 89 | ->andReturn(true); 90 | 91 | // Create links between persons and conversation 92 | $this->driver->shouldReceive('createConversationPersonModel') 93 | ->once() 94 | ->with($conversation, $from) 95 | ->andReturn($fromConversationModel); 96 | 97 | $this->driver->shouldReceive('persistConversationPerson') 98 | ->once() 99 | ->with($fromConversationModel) 100 | ->andReturn(true); 101 | 102 | $this->driver->shouldReceive('createConversationPersonModel') 103 | ->once() 104 | ->with($conversation, $firstRecipient) 105 | ->andReturn($firstConversationModel); 106 | 107 | $this->driver->shouldReceive('persistConversationPerson') 108 | ->once() 109 | ->with($firstConversationModel) 110 | ->andReturn(true); 111 | 112 | $this->driver->shouldReceive('createConversationPersonModel') 113 | ->once() 114 | ->with($conversation, $secondRecipient) 115 | ->andReturn($secondConversationModel); 116 | 117 | $this->driver->shouldReceive('persistConversationPerson') 118 | ->once() 119 | ->with($secondConversationModel) 120 | ->andReturn(true); 121 | 122 | // Create links between persons and first message 123 | $this->driver->shouldReceive('createMessagePersonModel') 124 | ->once() 125 | ->with($message, $from) 126 | ->andReturn($fromMessageModel); 127 | 128 | $this->driver->shouldReceive('persistMessagePerson') 129 | ->once() 130 | ->with($fromMessageModel) 131 | ->andReturn(true); 132 | 133 | $this->driver->shouldReceive('createMessagePersonModel') 134 | ->once() 135 | ->with($message, $firstRecipient) 136 | ->andReturn($firstMessageModel); 137 | 138 | $this->driver->shouldReceive('persistMessagePerson') 139 | ->once() 140 | ->with($firstMessageModel) 141 | ->andReturn(true); 142 | 143 | $this->driver->shouldReceive('createMessagePersonModel') 144 | ->once() 145 | ->with($message, $secondRecipient) 146 | ->andReturn($secondMessageModel); 147 | 148 | $this->driver->shouldReceive('persistMessagePerson') 149 | ->once() 150 | ->with($secondMessageModel) 151 | ->andReturn(true); 152 | 153 | // Dispatcher 154 | $this->dispatcher->shouldReceive('dispatch') 155 | ->with( 156 | EventDispatcherInterface::START_CONVERSATION_PRE_PERSIST, 157 | Mockery::type('FOS\Message\Event\ConversationEvent') 158 | ) 159 | ->once() 160 | ->andReturn(true); 161 | 162 | // Final flush 163 | $this->driver->shouldReceive('flush') 164 | ->once() 165 | ->withNoArgs() 166 | ->andReturn(true); 167 | 168 | // Dispatcher 169 | $this->dispatcher->shouldReceive('dispatch') 170 | ->with( 171 | EventDispatcherInterface::START_CONVERSATION_POST_PERSIST, 172 | Mockery::type('FOS\Message\Event\ConversationEvent') 173 | ) 174 | ->once() 175 | ->andReturn(true); 176 | 177 | $this->assertSame( 178 | $conversation, 179 | $this->sender->startConversation( 180 | $from, 181 | [$firstRecipient, $secondRecipient], 182 | 'Body', 183 | 'Subject' 184 | ) 185 | ); 186 | } 187 | 188 | public function testSendMessage() 189 | { 190 | $from = Mockery::mock('FOS\Message\Model\PersonInterface'); 191 | $from->shouldReceive('getId')->withNoArgs()->once()->andReturn(1); 192 | 193 | $firstRecipient = Mockery::mock('FOS\Message\Model\PersonInterface'); 194 | $firstRecipient->shouldReceive('getId')->withNoArgs()->once()->andReturn(2); 195 | 196 | $secondRecipient = Mockery::mock('FOS\Message\Model\PersonInterface'); 197 | $secondRecipient->shouldReceive('getId')->withNoArgs()->once()->andReturn(2); 198 | 199 | $fromConversationModel = Mockery::mock('FOS\Message\Model\ConversationPersonInterface'); 200 | $fromConversationModel->shouldReceive('getPerson')->withNoArgs()->once()->andReturn($from); 201 | 202 | $firstConversationModel = Mockery::mock('FOS\Message\Model\ConversationPersonInterface'); 203 | $firstConversationModel->shouldReceive('getPerson')->withNoArgs()->once()->andReturn($firstRecipient); 204 | 205 | $secondConversationModel = Mockery::mock('FOS\Message\Model\ConversationPersonInterface'); 206 | $secondConversationModel->shouldReceive('getPerson')->withNoArgs()->once()->andReturn($secondRecipient); 207 | 208 | $conversation = Mockery::mock('FOS\Message\Model\ConversationInterface'); 209 | $conversation->shouldReceive('getConversationPersons') 210 | ->withNoArgs() 211 | ->andReturn([$fromConversationModel, $firstConversationModel, $secondConversationModel]); 212 | 213 | $fromMessageModel = Mockery::mock('FOS\Message\Model\MessagePersonInterface'); 214 | $fromMessageModel->shouldReceive('setRead')->withNoArgs()->andReturnSelf(); 215 | 216 | $firstMessageModel = Mockery::mock('FOS\Message\Model\MessagePersonInterface'); 217 | $secondMessageModel = Mockery::mock('FOS\Message\Model\MessagePersonInterface'); 218 | 219 | $message = Mockery::mock('FOS\Message\Model\MessageInterface'); 220 | 221 | // Create the message 222 | $this->driver->shouldReceive('createMessageModel') 223 | ->once() 224 | ->with($conversation, $from, 'ReplyBody') 225 | ->andReturn($message); 226 | 227 | $this->driver->shouldReceive('persistMessage') 228 | ->once() 229 | ->with($message) 230 | ->andReturn(true); 231 | 232 | // Create links between persons and message 233 | $this->driver->shouldReceive('createMessagePersonModel') 234 | ->once() 235 | ->with($message, $from) 236 | ->andReturn($fromMessageModel); 237 | 238 | $this->driver->shouldReceive('persistMessagePerson') 239 | ->once() 240 | ->with($fromMessageModel) 241 | ->andReturn(true); 242 | 243 | $this->driver->shouldReceive('createMessagePersonModel') 244 | ->once() 245 | ->with($message, $firstRecipient) 246 | ->andReturn($firstMessageModel); 247 | 248 | $this->driver->shouldReceive('persistMessagePerson') 249 | ->once() 250 | ->with($firstMessageModel) 251 | ->andReturn(true); 252 | 253 | $this->driver->shouldReceive('createMessagePersonModel') 254 | ->once() 255 | ->with($message, $secondRecipient) 256 | ->andReturn($secondMessageModel); 257 | 258 | $this->driver->shouldReceive('persistMessagePerson') 259 | ->once() 260 | ->with($secondMessageModel) 261 | ->andReturn(true); 262 | 263 | // Dispatcher 264 | $this->dispatcher->shouldReceive('dispatch') 265 | ->with( 266 | EventDispatcherInterface::SEND_MESSAGE_PRE_PERSIST, 267 | Mockery::type('FOS\Message\Event\MessageEvent') 268 | ) 269 | ->once() 270 | ->andReturn(true); 271 | 272 | // Final flush 273 | $this->driver->shouldReceive('flush') 274 | ->once() 275 | ->withNoArgs() 276 | ->andReturn(true); 277 | 278 | // Dispatcher 279 | $this->dispatcher->shouldReceive('dispatch') 280 | ->with( 281 | EventDispatcherInterface::SEND_MESSAGE_POST_PERSIST, 282 | Mockery::type('FOS\Message\Event\MessageEvent') 283 | ) 284 | ->once() 285 | ->andReturn(true); 286 | 287 | $this->assertSame( 288 | $message, 289 | $this->sender->sendMessage( 290 | $conversation, 291 | $from, 292 | 'ReplyBody' 293 | ) 294 | ); 295 | } 296 | 297 | /** 298 | * @expectedException \InvalidArgumentException 299 | */ 300 | public function testStartConversationInvalidSingleRecipient() 301 | { 302 | $from = Mockery::mock('FOS\Message\Model\PersonInterface'); 303 | $this->sender->startConversation($from, 'invalid', 'Body', 'Subject'); 304 | } 305 | 306 | /** 307 | * @expectedException \InvalidArgumentException 308 | */ 309 | public function testStartConversationInvalidMultipleRecipient() 310 | { 311 | $from = Mockery::mock('FOS\Message\Model\PersonInterface'); 312 | $this->sender->startConversation($from, ['invalid'], 'Body', 'Subject'); 313 | } 314 | 315 | /** 316 | * @expectedException \InvalidArgumentException 317 | */ 318 | public function testStartConversationInvalidBody() 319 | { 320 | $from = Mockery::mock('FOS\Message\Model\PersonInterface'); 321 | $to = Mockery::mock('FOS\Message\Model\PersonInterface'); 322 | $this->sender->startConversation($from, $to, new \stdClass(), 'Subject'); 323 | } 324 | 325 | /** 326 | * @expectedException \InvalidArgumentException 327 | */ 328 | public function testStartConversationInvalidSubject() 329 | { 330 | $from = Mockery::mock('FOS\Message\Model\PersonInterface'); 331 | $to = Mockery::mock('FOS\Message\Model\PersonInterface'); 332 | $this->sender->startConversation($from, $to, 'Body', new \stdClass()); 333 | } 334 | 335 | /** 336 | * @expectedException \InvalidArgumentException 337 | */ 338 | public function testSendMessageInvalidBody() 339 | { 340 | $conversation = Mockery::mock('FOS\Message\Model\ConversationInterface'); 341 | $from = Mockery::mock('FOS\Message\Model\PersonInterface'); 342 | $this->sender->sendMessage($conversation, $from, new \stdClass()); 343 | } 344 | } 345 | -------------------------------------------------------------------------------- /tests/TaggerTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace FOS\Message\Tests; 13 | 14 | use FOS\Message\Driver\DriverInterface; 15 | use FOS\Message\RepositoryInterface; 16 | use FOS\Message\Tagger; 17 | use Mockery; 18 | use Mockery\MockInterface; 19 | use PHPUnit_Framework_TestCase; 20 | 21 | class TaggerTest extends PHPUnit_Framework_TestCase 22 | { 23 | /** 24 | * @var DriverInterface|MockInterface 25 | */ 26 | private $driver; 27 | 28 | /** 29 | * @var RepositoryInterface|MockInterface 30 | */ 31 | private $repository; 32 | 33 | /** 34 | * @var Tagger 35 | */ 36 | private $tagger; 37 | 38 | /** 39 | * {@inheritdoc} 40 | */ 41 | protected function setUp() 42 | { 43 | $this->driver = Mockery::mock('FOS\Message\Driver\DriverInterface'); 44 | $this->repository = Mockery::mock('FOS\Message\RepositoryInterface'); 45 | $this->tagger = new Tagger($this->driver, $this->repository); 46 | } 47 | 48 | public function testAddNewTag() 49 | { 50 | $person = Mockery::mock('FOS\Message\Model\PersonInterface'); 51 | $conversation = Mockery::mock('FOS\Message\Model\ConversationInterface'); 52 | $tag = Mockery::mock('FOS\Message\Model\TagInterface'); 53 | 54 | $conversationPerson = Mockery::mock('FOS\Message\Model\ConversationPersonInterface'); 55 | $conversationPerson->shouldReceive('hasTag')->with($tag)->once()->andReturn(false); 56 | $conversationPerson->shouldReceive('addTag')->with($tag)->once()->andReturnSelf(); 57 | 58 | $this->repository->shouldReceive('getConversationPerson') 59 | ->once() 60 | ->with($conversation, $person) 61 | ->andReturn($conversationPerson); 62 | 63 | $this->driver->shouldReceive('persistConversationPerson') 64 | ->once() 65 | ->with($conversationPerson) 66 | ->andReturn(true); 67 | 68 | $this->driver->shouldReceive('flush') 69 | ->once() 70 | ->withNoArgs() 71 | ->andReturn(true); 72 | 73 | $this->assertTrue($this->tagger->addTag($conversation, $person, $tag)); 74 | } 75 | 76 | public function testAddExistingTag() 77 | { 78 | $person = Mockery::mock('FOS\Message\Model\PersonInterface'); 79 | $conversation = Mockery::mock('FOS\Message\Model\ConversationInterface'); 80 | $tag = Mockery::mock('FOS\Message\Model\TagInterface'); 81 | 82 | $conversationPerson = Mockery::mock('FOS\Message\Model\ConversationPersonInterface'); 83 | $conversationPerson->shouldReceive('hasTag')->with($tag)->once()->andReturn(true); 84 | 85 | $this->repository->shouldReceive('getConversationPerson') 86 | ->once() 87 | ->with($conversation, $person) 88 | ->andReturn($conversationPerson); 89 | 90 | $this->assertFalse($this->tagger->addTag($conversation, $person, $tag)); 91 | } 92 | 93 | public function testRemoveExistingTag() 94 | { 95 | $person = Mockery::mock('FOS\Message\Model\PersonInterface'); 96 | $conversation = Mockery::mock('FOS\Message\Model\ConversationInterface'); 97 | $tag = Mockery::mock('FOS\Message\Model\TagInterface'); 98 | 99 | $conversationPerson = Mockery::mock('FOS\Message\Model\ConversationPersonInterface'); 100 | $conversationPerson->shouldReceive('hasTag')->with($tag)->once()->andReturn(true); 101 | $conversationPerson->shouldReceive('removeTag')->with($tag)->once()->andReturn(true); 102 | 103 | $this->repository->shouldReceive('getConversationPerson') 104 | ->once() 105 | ->with($conversation, $person) 106 | ->andReturn($conversationPerson); 107 | 108 | $this->driver->shouldReceive('persistConversationPerson') 109 | ->once() 110 | ->with($conversationPerson) 111 | ->andReturn(true); 112 | 113 | $this->driver->shouldReceive('flush') 114 | ->once() 115 | ->withNoArgs() 116 | ->andReturn(true); 117 | 118 | $this->assertTrue($this->tagger->removeTag($conversation, $person, $tag)); 119 | } 120 | 121 | public function testRemoveNotExistingTag() 122 | { 123 | $person = Mockery::mock('FOS\Message\Model\PersonInterface'); 124 | $conversation = Mockery::mock('FOS\Message\Model\ConversationInterface'); 125 | $tag = Mockery::mock('FOS\Message\Model\TagInterface'); 126 | 127 | $conversationPerson = Mockery::mock('FOS\Message\Model\ConversationPersonInterface'); 128 | $conversationPerson->shouldReceive('hasTag')->with($tag)->once()->andReturn(false); 129 | 130 | $this->repository->shouldReceive('getConversationPerson') 131 | ->once() 132 | ->with($conversation, $person) 133 | ->andReturn($conversationPerson); 134 | 135 | $this->assertFalse($this->tagger->removeTag($conversation, $person, $tag)); 136 | } 137 | } 138 | --------------------------------------------------------------------------------