I have been having to make a lot of web service end points in Typo3 recently and it drives me mad that there isn’t an out of the box solution in Typo3. It just seems crazy.
So, I though I’d write down the way I’m doing it, to help out anyone else who is struggling.
Background
I’ve seen lots of discussions about it and no-one seems to be able to come up with a good way to do it. The two standard options are PageType and eID.
There are problems with both of these approaches, first with the PageType approach you have to configure your endpoint in TypoScript, and you also have to keep track of the PageType numbers.
The problem with eID scripts is that they are intentionally a very lightweight way of having an endpoint in Typo3. To keep them very responsive you don’t have any of the frontend code that gives you all the useful functionality provided by Typo3 Core and Extbase.
A Third Way
A third way has recently been talked about by Helmut Hummel, that he calls TypoScript Rendering.
To illustrate is method, he has created a nice demo page to demonstrate how fast the responses are using typoscript_rendering. You can see it here: http://ajax.helmut-hummel.de/
But looking at the links in those pages, the eID url is as follows:
http://ajax.helmut-hummel.de/index.php?eID=ajax_example&tx_ajaxexample_piexample[format]=json
And the typo3script_rendering URL is this:
http://ajax.helmut-hummel.de/index.php?id=1&tx_typoscriptrendering%5Bcontext%5D=%7B%22record%22%3A%22tt_content_1%22%2C%22path%22%3A%22tt_content.list.20.ajaxexample_piexample%22%7D&tx_ajaxexample_piexample%5Baction%5D=hello&tx_ajaxexample_piexample%5Bcontroller%5D=Example&tx_ajaxexample_piexample%5Bformat%5D=json&cHash=da7930dfe6e8787d2ff39f25693b48f6
I know which one I would want to give to a 3rd party developer.
I think it is a clever solution, and does get around the problems with the other two methods mentioned above, but in my opinion it has a major drawback in that it seems limited in use to internal ajax requests. He uses fluid templates to generate the ajax urls, but what happens if you can’t use typolinks?
What if your webservice is being called from a page that doesn’t know anything about Typo3?
What if you are providing an API for 3rd parties?
And what if I want to send raw output back and not bother with fluid or Extbase views at all?
I’m sure there is a way around it but it’s not obvious to me.
Implementation
So after all that talk, here is my way of implementing a web service using the eID mechanism, but enabling the script to make use of the Extbase Models and Repositories that we have lovingly coded and have unit tests for.
Not much of this is original, I have just collected some people’s ideas and made them in to something that works for me. You can find a list of links that I used for inspiration at the bottom of the page.
You can find all the code for this example in an Typo3 extension on GitHub, extbase-eid-webservice.
First register the eID in the ext_localconf.php
, and define the php file that will handle the eID request.
// Register eID script
$GLOBALS['TYPO3_CONF_VARS']['FE']['eID_include']['tx_example'] = 'EXT:'. $_EXTKEY .'/Resources/Private/Php/tx_example_eid.php';
In the tx_example_eid.php
, I instantiate a new instance of a class EidBootstrap
that i’ll define shortly…
...
/** @var Acme\Example\Core\EidBootstrap $bootstrap */
$bootstrap = TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('Acme\Example\Core\EidBootstrap', 'tx_example');
// Use the Extbase bootstrap object to load the correct typoscript config, including storagePids for classes.
$configuration = array(
'vendorName' => 'Acme',
'extensionName' => 'Example',
'extensionKey' => 'tx_example',
'pluginName' => 'Pi1'
);
$bootstrap->setExtensionConfiguration($configuration);
$bootstrap->initialize();
...
This bootstrap is the key class that allows us to set up the TypoScriptFrontendController, it is a wrapper for functionality provided by the Extbase bootstrap.
The config that I’m passing in is there so that the TypoScriptFrontendController object can retrieve the TypoScript values from the extensions config files.
If you are installing my extension, you’ll need to include the extension TypoScript file in the main template in the typo3 backend. I haven’t added it automatically.
Being able to read the extension’s TypoScript is important for me because that is where I use the Extbase storage Id parameter, and the repositories need to know which PID to look for the records in. You can see the settings in the constants.txt
plugin.tx_example {
...
persistence {
# cat=plugin.tx_example//a; type=string; label=Default storage PID
storagePid = 1
# cat=plugin.tx_example//a; type=string; label=Blog model storage PID
blogStoragePid = 1
# cat=plugin.tx_example//a; type=string; label=Post model storage PID
postStoragePid = 1
}
}
And in setup.txt
…
plugin.tx_example {
persistence {
storagePid = {$plugin.tx_example.persistence.storagePid}
classes {
Acme\Example\Domain\Model\Blog {
# cat=plugin.tx_example//a; type=string; label=Blog model new record storage PID
newRecordStoragePid = {$plugin.tx_example.persistence.blogStoragePid}
}
Acme\Example\Domain\Model\Post {
# cat=plugin.tx_example//a; type=string; label=Post model new record storage PID
newRecordStoragePid = {$plugin.tx_example.persistence.postStoragePid}
}
}
}
...
}
In the EidBootstrap
class, this configuration is used to create a Extbase bootstrap object that knows about the persistence settings for the classes. This is important, as I might want to create Domain Model objects and have them persisted with the correct place without hardcoding the pids in my web service.
First I create a new TypoScriptFrontendController
…
...
/** @var TypoScriptFrontendController $typoScriptFrontendController */
$typoScriptFrontendController = GeneralUtility::makeInstance(
'TYPO3\CMS\FrontendController\TypoScriptFrontendController',
$GLOBALS['TYPO3_CONF_VARS'],
$pageId,
0,
TRUE
);
// Call all the methods that set up the Typo3 frontend.
$GLOBALS['TSFE'] = $typoScriptFrontendController;
$typoScriptFrontendController->connectToDB();
$typoScriptFrontendController->fe_user = $feUserObj;
$typoScriptFrontendController->id = $pageId;
$typoScriptFrontendController->determineId();
$typoScriptFrontendController->getCompressedTCarray();
$typoScriptFrontendController->initTemplate();
$typoScriptFrontendController->getConfigArray();
$typoScriptFrontendController->includeTCA();
...
By the way, I totally aggree with Helmut Hummel here; this is an error prone way to do set up what needs to be done. But, I do not understand why there isn’t a public API in the Typo3 Core that can set this up and provide a controller ready to use.
Until there is it has to be done this way.
Now grab the TypoScript:
...
// Now get the TypoScript from the frontend controller.
/** @var TypoScriptService $typoScriptService */
$typoScriptService = GeneralUtility::makeInstance('TYPO3\CMS\Extbase\Service\TypoScriptService');
$pluginTyposcript = $typoScriptFrontendController->tmpl->setup['plugin.'][$this->extensionConfiguration['extensionKey'] . '.'];
$this->pluginConfiguration = $typoScriptService->convertTypoScriptArrayToPlainArray($pluginTyposcript);
// Set configuration to call the plugin
$extensionConfiguration['settings'] = $this->pluginConfiguration['settings'];
$extensionConfiguration['persistence'] = $this->pluginConfiguration['persistence'];
$this->extensionConfiguration = array_merge($extensionConfiguration, $this->extensionConfiguration);
// Create an Extbase Bootstrap object and initialize it.
$this->bootstrap = GeneralUtility::makeInstance('TYPO3\CMS\Extbase\Core\Bootstrap');
$this->bootstrap->initialize($this->extensionConfiguration);
...
Once the bootstrap is initialized, it’s easy to create an object manager and create anyother classes you need. Back in the tx_example_eid.php
:
...
// Need to create the object manager here so it is available for everything else.
$objectManager = TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\CMS\Extbase\Object\ObjectManager');
$webService = $objectManager->get('Acme\Example\Web\Service');
$webService->handle();
Because the extension TypoScript has been loaded correctly, when I inject the BlogRepository
in the WebService
class, (this is automatic because I am creating it with the ObjectManager) the repository knows where to load the Blogs from.
...
/**
* @param Acme\Example\Domain\Repository\BlogRepository
*/
public function injectBlogRepository(BlogRepository $blogRepository) {
$this->blogRepository = $blogRepository;
}
...
Then it’s trivial to load the blogs from the Repository and turn them in to a JSON response.
...
protected function blogsAction() {
header('Content-Type: application/json');
$blogs = $this->blogRepository->findAll();
foreach($blogs as $blog) {
echo json_encode($blog->toJSON()) . "n";
}
}
...
To make it easier to persist any objects that change, the WebService
class injects the persistence manager and has a shutdown()
method that persists changes to the db.
...
/**
* We need the persistence manager as it is used in the shutdown function
* @param TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager $persistenceManager
*/
public function injectPersistenceManager(PersistenceManager $persistenceManager) {
$this->persistenceManager = $persistenceManager;
}
...
I can call the endpoint on my development set up at the following URL
http://localhost/index.php?eID=tx_example
Summary
That should be enough to use Extbase models and Repositories in an eID endpoint script. It’s not the optimal way and it doesn’t take advantage of some of Typo3’s built in Caching, but it does let you use the domain model logic and persistence mechanisms from Extbase that I really like.
As always when developing software, there are lots of ways to get something done, and it’s about choosing the right way at the right time. I just hope that this post might help someone out at some point in the future.
Other options
If you don’t want to roll your own, there are a few extensions that provide REST functionality in the Typo3 world, my favourite is Cundd REST, that you can set up to map url paths to Extbase models directly. It’s well coded and nice to use, but can be harder to build simple customised services.
Reference Links
- http://lbrmedia.net/codebase/Eintrag/extbase-eid-bootstrap/
- http://insight.helhum.io/post/90365109335/out-of-bound-typoscript-rendering
- http://mimi.kaktusteam.de/blog-posts/2012/05/using-extbase-for-ajax-requests/
- http://daniel.lienert.cc/blog/blog-post/2011/04/23/typo3-extbase-und-ajax/
- http://t3-developer.com/extbase-fluid/extensions-erweitern/ajax-in-extensions/ajax-dispatcher-eid-in-typo3-61/
And here’s a link to my extension again - https://github.com/timrross/extbase-eid-webservice