structural changes

This commit is contained in:
Bernhard Posselt
2013-03-12 17:20:15 +01:00
parent a06e8d9a12
commit 7073f29087
57 changed files with 401 additions and 291 deletions

View File

@@ -0,0 +1,4 @@
JavaScript
==========
.. sectionauthor:: Bernhard Posselt <nukeawhale@gmail.com>

View File

@@ -0,0 +1,71 @@
Runtime configuration
=====================
.. sectionauthor:: Bernhard Posselt <nukeawhale@gmail.com>
Dependency Injection helps you to create testable code. A good overview over how it works and what the benefits are can be seen on `Google's Clean Code Talks <http://www.youtube.com/watch?v=RlfLCWKxHJ0>`_
The container is configured in :file:`dependencyinjection/dicontainer.php`. By default `Pimple <http://pimple.sensiolabs.org/>`_ is used as dependency injection container. A `tutorial can be found here <http://jtreminio.com/2012/10/an-introduction-to-pimple-and-service-containers/>`_
To add your own classes simply open the :file:`dependencyinjection/dicontainer.php` and add a line like this to the constructor:
.. code-block:: php
<?php
// in the constructor
$this['MyClass'] = function($c){
return new MyClass($c['SomeOtherClass']);
};
?>
You can also overwrite already existing items from the App Framework simply by redefining them.
**See also** :doc:`../general/dependencyinjection`
API abstraction layer
=====================
.. sectionauthor:: Bernhard Posselt <nukeawhale@gmail.com>
ownCloud currently has a ton of static methods which is a very bad thing concerning testability. Therefore the App Framework comes with an :php:class:`OCA\\AppFramework\\Core\\API` abstraction layer (basically a `facade <http://en.wikipedia.org/wiki/Facade_pattern>`_) which is located in the App Framework app at :file:`core/api.php`.
This is a temporary solution until ownCloud offers a proper API with normal classes that can be used in the DIContainer.
This will allow you to easily mock the API in your unittests.
If you find yourself in need to use more ownCloud internal static methods, add them to the API class in the **appframework/** directory, like:
.. code-block:: php
<?php
// inside the API class
public function methodName($someParam){
\OCP\Util::methodName($this->appName, $someParam);
}
}
?>
.. note:: Please send a pull request and cc **Raydiation** so the method can be added to the API class.
A temporary solution would be to to simply inherit from the API class and overwrite the API in the dependency injection container in :file:`dependencyinjection/dicontainer.php` by using:
.. code-block:: php
<?php
// inside the constructor
$this['API'] = $this->share(function($c){
return new MyExtendedAPI($c['AppName']);
});

View File

@@ -0,0 +1,154 @@
Controllers
===========
.. sectionauthor:: Bernhard Posselt <nukeawhale@gmail.com>
The App Framework provides a simple baseclass for adding controllers: :php:class:`OCA\\AppFramework\\Controller\\Controller`. Controllers connect your view (templates) with your database. Controllers themselves are connected to one or more routes. Controllers go into the **controller/** directory.
A controller should be created for each resource. Think of it as an URL scheme::
/controller/method/params
For instance::
/file/delete/1
In this case we would create a controller named **FileController** and the method would be called **delete()**.
The apptemplate comes with several different controllers. A simple controller would look like:
.. code-block:: php
<?php
namespace OCA\YourApp\Controller;
use \OCA\AppFramework\Controller\Controller;
use \OCA\AppFramework\Http\JSONResponse;
class MyController extends Controller {
/**
* @param Request $request an instance of the request
* @param API $api an api wrapper instance
*/
public function __construct($api, $request){
parent::__construct($api, $request);
}
/**
* @Ajax
*
* sets a global system value
*/
public function myControllerMethod(){
$value = $this->params('somesetting');
$response = new JSONResponse();
$response->setParams(array('value' => $value));
return $response;
}
}
?>
An instance of the API is passed via :doc:`../general/dependencyinjection`, the same goes for a :php:class:`OCA\\AppFramework\\Http\\Request` instance. URL Parameters, POST, GET and FILES parameters are partly abstracted by the Request class and can be accessed via **$this->params('myURLParamOrPostOrGetKey')** and **$this->getUploadedFile($key)** inside the controller. This has been done to make the app better testable.
.. note:: If an URL Parameter, POST or GET value exist with the same key, the URL Parameter is preferred over the POST parameter and the POST parameter is preferred over the GET parameter. You should avoid this scenario though.
Every controller method has to return a Response object. The currently available Responses from the App Framework include:
* :php:class:`OCA\\AppFramework\\Http\\Response`: response for sending headers only
* :php:class:`OCA\\AppFramework\\Http\\JSONResponse`: sends JSON to the client
* :php:class:`OCA\\AppFramework\\Http\\TemplateResponse`: renders a template
* :php:class:`OCA\\AppFramework\\Http\\RedirectResponse`: redirects to a new URL
* :php:class:`OCA\\AppFramework\\Http\\TextDownloadResponse`: prompts the user to download a text file containing a passed string
* :php:class:`OCA\\AppFramework\\Http\\TextResponse`: for printing text like XML
Should you require to set additional headers, you can use the :php:meth:`OCA\\AppFramework\\Http\\Response::addHeader` method that every Response has.
Because TemplateResponse and JSONResponse are so common, the controller provides a shortcut method for both of them, namely **$this->render** and **$this->renderJSON**.
.. code-block:: php
<?
/**
* @CSRFExemption
*/
public function index(){
$templateName = 'main';
$params = array(
'somesetting' => 'How long will it take'
);
return $this->render($templateName, $params);
}
/**
* @Ajax
*/
public function getMeJSON(){
$params = array(
'somesetting' => 'enough of this already'
);
return $this->renderJSON($params);
}
For security reasons, all security checks for controller methods are turned on by default. To explicitely turn off checks, you must use exemption annotations above the desired method.
In this example, all security checks would be disabled (**not recommended**):
.. code-block:: php
<?php
/**
* @CSRFExemption
* @IsAdminExemption
* @IsLoggedInExemption
* @IsSubAdminExemption
*/
public function index(){
$templateName = 'main';
$params = array(
'somesetting' => 'How long will it take'
);
return $this->render($templateName, $params);
}
Possible Annotations contain:
* **@CSRFExemption**: Turns off the check for the `CSRF <http://en.wikipedia.org/wiki/Cross-site_request_forgery>`_ token. **Only use this for the index page**!
* **@IsAdminExemption**: Turns off the check if the user is an admin
* **@IsLoggedInExemption**: Turns off the check if the user is logged in
* **@IsSubAdminExemption**: Turns off the check if the user is a subadmin
* **@Ajax**: Use this for Ajax Requests. It prevents the unneeded rendering of the apps navigation and returns error messages in JSON format
Don't forget to add your controller to the dependency injection container in :file:`dependencyinjection/dicontainer.php`
.. code-block:: php
<?php
// in the constructor function
$this['MyController'] = function($c){
return new MyController($c['API'], $c['Request']);
};
?>

View File

@@ -0,0 +1,205 @@
Database Access
===============
.. sectionauthor:: Bernhard Posselt <nukeawhale@gmail.com>
Your database layer should go into the **db/** folder. It's recommended to split your data entities from your database queries. You can do that by creating a very simple PHP object with getters and setters. This object will hold your data.
:file:`db/item.php`
.. code-block:: php
<?php
namespace \OCA\YourApp\Db;
class Item {
private $id;
private $name;
private $path;
private $user;
public function __construct($fromRow=null){
if($fromRow){
$this->fromRow($fromRow);
}
}
public function fromRow($row){
$this->id = $row['id'];
$this->name = $row['name'];
$this->path = $row['path'];
$this->user = $row['user'];
}
public function getId(){
return $this->id;
}
public function getName(){
return $this->name;
}
public function getUser(){
return $this->user;
}
public function getPath(){
return $this->path;
}
public function setId($id){
$this->id = $id;
}
public function setName($name){
$this->name = $name;
}
public function setUser($user){
$this->user = $user;
}
public function setPath($path){
$this->path = $path;
}
}
All database queries for that object should be put into a mapper class. This follows the `data mapper pattern <http://www.martinfowler.com/eaaCatalog/dataMapper.html>`_. The mapper class could look like this (more method examples are in the **Apptemplate Advanced** app):
:file:`db/itemmapper.php`
.. code-block:: php
<?php
namespace \OCA\YourApp\Db;
use \OCA\AppFramework\Db\DoesNotExistException;
use \OCA\AppFramework\Db\Mapper;
class ItemMapper extends Mapper {
private $tableName;
/**
* @param API $api Instance of the API abstraction layer
*/
public function __construct($api){
parent::__construct($api);
$this->tableName = '*PREFIX*apptemplateadvanced_items';
}
/**
* Finds an item by id
* @throws DoesNotExistException if the item does not exists
* @throws MultipleObjectsReturnedException if more than one item exists
* @return Item the item
*/
public function find($id){
$row = $this->findQuery($this->tableName, $id);
return new Item($row);
}
/**
* Finds an item by user id
* @param string $userId the id of the user that we want to find
* @throws DoesNotExistException if the item does not exist
* @return Item the item
*/
public function findByUserId($userId){
$sql = 'SELECT * FROM ' . $this->tableName . ' WHERE user = ?';
$params = array($userId);
$result = $this->execute($sql, $params)->fetchRow();
if($result){
return new Item($result);
} else {
throw new DoesNotExistException('Item with user id ' . $userId . ' does not exist!');
}
}
/**
* Saves an item into the database
* @param Item $item the item to be saved
* @return Item the item with the filled in id
*/
public function save($item){
$sql = 'INSERT INTO '. $this->tableName . '(name, user, path)'.
' VALUES(?, ?, ?)';
$params = array(
$item->getName(),
$item->getUser(),
$item->getPath()
);
$this->execute($sql, $params);
$item->setId($this->api->getInsertId());
return $item;
}
/**
* Updates an item
* @param Item $item: the item to be updated
*/
public function update($item){
$sql = 'UPDATE '. $this->tableName . ' SET
name = ?,
user = ?,
path = ?
WHERE id = ?';
$params = array(
$item->getName(),
$item->getUser(),
$item->getPath(),
$item->getId()
);
$this->execute($sql, $params);
}
/**
* Deletes an item
* @param int $id the id of the item
*/
public function delete($id){
$this->deleteQuery($this->tableName, $id);
}
}
.. note:: Always use **?** to mark placeholders for arguments in SQL queries and pass the arguments as a second parameter to the execute function to prevent `SQL Injection <http://php.net/manual/en/security.database.sql-injection.php>`_
**DONT**:
.. code-block:: php
<?php
$sql = 'SELECT * FROM ' . $this->tableName . ' WHERE user = ' . $user;
$result = $this->execute($sql);
**DO**:
.. code-block:: php
<?php
$sql = 'SELECT * FROM ' . $this->tableName . ' WHERE user = ?';
$params = array($userId);
$result = $this->execute($sql, $params);

View File

@@ -0,0 +1,25 @@
========================================
App Developement Using the App Framework
========================================
.. toctree::
:maxdepth: 1
tutorial
../app/info
../app/classloader
container
routes
controllers
../app/schema
database
templates
../app/css
../app/javascript
angular
unittesting
middleware
../app/externalapi
../app/filesystem
../app/hooks
../app/data-migration

View File

@@ -0,0 +1,68 @@
Middleware
==========
.. sectionauthor:: Bernhard Posselt <nukeawhale@gmail.com>
Middleware is logic that is run before and after each request and is modelled after `Django's Middleware system <https://docs.djangoproject.com/en/dev/topics/http/middleware/>`_. It offers the following hooks:
* **beforeController**: This is executed before a controller method is being executed. This allows you to plug additional checks or logic before that method, like for instance security checks
* **afterException**: This is being run when either the beforeController method or the controller method itself is throwing an exception. The middleware is asked in reverse order to handle the exception and to return a response. If the response is null, it is assumed that the exception could not be handled and the error will be thrown again
* **afterController**: This is being run after a successful controllermethod call and allows the manipulation of a Response object. The middleware is run in reverse order
* **beforeOutput**: This is being run after the response object has been rendered and allows the manipulation of the outputted text. The middleware is run in reverse order
To generate your own middleware, simply inherit from the Middleware class :php:class:`OCA\\AppFramework\\Middleware\\Middleware`: and overwrite the methods that you want to use.
.. code-block:: php
<?php
use \OCA\AppFramework\Middleware\Middleware;
class CensorMiddleware extends Middleware {
private $api;
/**
* @param API $api an instance of the api
*/
public function __construct($api){
$this->api = $api;
}
/**
* this replaces "fuck" with "****"" in the output
*/
public function beforeOutput($controller, $methodName, $output){
return str_replace($output, 'fuck', '****');
}
}
To activate the middleware, you have to overwrite the :php:class:`OCA\\AppFramework\\Middleware\\MiddlewareDispatcher`: in the DIContainer constructor:
.. note:: If you ship your own middleware, be sure to also enable the existing ones like the **SecurityMiddleware** if you overwrite the MiddlewareDispatcher in the Dependency Injection Container! **If this is forgotten, there will be security issues**!
.. code-block:: php
<?php
// in the constructor
$this['CensorMiddleware'] = function($c){
return new CensorMiddleware($c['API']);
};
$this['MiddlewareDispatcher'] = function($c){
$dispatcher = new \OCA\AppFramework\Middleware\MiddlewareDispatcher();
$dispatcher->registerMiddleware($c['SecurityMiddleware']);
$dispatcher->registerMiddleware($c['CensorMiddleware']);
return $dispatcher;
};
.. note::
The order is important! The middleware that is registered first gets run first in the **beforeController** method. For all other hooks, the order is being reversed, meaning: if a middleware is registered first, it gets run last.

View File

@@ -0,0 +1,116 @@
Routes
======
.. sectionauthor:: Bernhard Posselt <nukeawhale@gmail.com>
PHP usually treats the URL like a filepath. This is easy for beginners but gets more complicated if a good architecture is required. For instance if an URL should call a certain function/method or if values should be extracted from the URL.
Routing connects your URLs with your controller methods and allows you to create constant and nice URLs. Its also easy to extract values from the URLs.
ownCloud uses `Symphony Routing <http://symfony.com/doc/2.0/book/routing.html>`_
Routes are declared in :file:`appinfo/routes.php`
A simple route would look like this:
.. code-block:: php
<?php
use \OCA\AppFramework\App;
use \OCA\YourApp\DependencyInjection\DIContainer;
// this route matches /index.php/yourapp/myurl/SOMEVALUE
$this->create('yourappname_routename', '/myurl/{key}')->action(
function($params){
App::main('MyController', 'methodName', $params, new DIContainer());
}
);
The first argument is the name of your route. This is used as an identifier to get the URL of the route and is a nice way to generate the URL in your templates or JavaScript for certain links since it does not force you to hardcode your URLs.
.. note:: The identifier should always start with the appid since they are global and you could overwrite a route of a different app
To use it in OC templates, use:
.. code-block:: php
<?
print_unescaped(\OC_Helper::linkToRoute( 'yourappname_routename', array('key' => 1)));
In Twig templates you can use the :js:func:`url` function:
.. code-block:: js
{{ url('yourappname_routename', {key: '1'}) }}
In JavaScript you can get the URL for a route like this:
.. code-block:: javascript
var params = {key: 1};
var url = OC.Router.generate('yourappname_routename', params);
console.log(url); // prints /index.php//yourappname/myurl/1
.. note:: Be sure to only use the routes generator after the routes are loaded. This can be done by registering a callback with **OC.Router.registerLoadedCallback(callback)**
The second parameter is the URL which should be matched. You can extract values from the URL by using **{key}** in the section that you want to get. That value is then available under **$params['key']**, for the above example it would be **$params['key']**. You can omit the parameter if you dont extract any values from the URL at all.
If a default value should be used for an URL parameter, it can be set via the **defaults** method:
.. code-block:: php
<?php
use \OCA\AppFramework\App;
use \OCA\YourApp\DependencyInjection\DIContainer;
$this->create('yourappname_routename', '/myurl/{key}')->action(
function($params){
App::main('MyController', 'methodName', $params, new DIContainer());
}
)->defaults('key' => 'john');
To call your controllers the App Framework provides a main method: :php:class:`OCA\\AppFramework\\App`.
The first parameter is the name under which the controller was defined in the :file:`dependencyinjection/dicontainer.php`.
The second parameter is the name of the method that should be called on the controller.
The third parameter is the $params array which is passed to the controller and available by using **$this->params($key)** in the controller method. In the following example, the parameter in the URL would be accessible by using: **$this->params('key')**
You can also limit the route to GET or POST requests by simply adding **->post()** or **->get()** before the action method like:
.. code-block:: php
<?php
use \OCA\AppFramework\App;
use \OCA\YourApp\DependencyInjection\DIContainer;
$this->create('yourappname_routename', '/myurl/{key}')->post()->action(
function($params){
App::main('MyController', 'methodName', $params, new DIContainer());
}
);
?>
The fourth parameter is an instance of the **DIContaier** (see :doc:`../general/dependencyinjection`). If you want to replace objects in the container only for a certain request, you can do it like this:
.. code-block:: php
<?php
use \OCA\AppFramework\App;
use \OCA\YourApp\DependencyInjection\DIContainer;
$this->create('yourappname_routename', '/myurl/{key}')->post()->action(
function($params){
$container = new DIContainer();
$container['SomeClass'] = function($c){
return new SomeClass('different');
}
App::main('MyController', 'methodName', $params, $container);
}
);
?>

View File

@@ -0,0 +1,157 @@
Templates
=========
.. sectionauthor:: Bernhard Posselt <nukeawhale@gmail.com>
ownCloud provides its own templating system. The App Framework also provides the option of using `Twig Templates <http://twig.sensiolabs.org/>`_ which can optionally be enabled. Templates reside in the **templates/** folder.
Templates are abstracted by the TemplateResponse object and used and returned inside the controller method. Variables can be assigned to the Template by using the :php:class:`OCA\\AppFramework\\Http\\TemplateResponse::setParams` method:
:file:`controllers/yourcontroller.php`
.. code-block:: php
<?php
use \OCA\AppFramework\Http\TemplateResponse;
// inside the controller
public function index(){
// main is the template name. Owncloud will look for template/main.php
$response = new TemplateResponse($this->api, 'main');
$params = array('entries' => array('this', 'is', 'your', 'father', 'speaking')
$response->setParams($params);
return $response;
}
Twig Templates (recommended)
----------------------------
ownCloud templates do a bad job at preventing `XSS <http://en.wikipedia.org/wiki/Cross-site_scripting>`_. Therefore the App Framework comes with a second option: the `Twig Templating Language <http://twig.sensiolabs.org/>`_.
Twig Templates are enabled by using the Twig Middleware. If a Twig template directory is set in the :file:`dependencyinjection/dicontainer.php`, the middleware gets loaded automatically. If no directory is set, theres no additional overhead.
To enable them in the :file:`dependencyinjection/dicontainer.php`, add the following line to the constructor:
.. code-block:: php
<?php
// in the constructor
// use this to specify the template directory
$this['TwigTemplateDirectory'] = __DIR__ . '/../templates';
Twig can also cache templates as simple PHP files. To make use of this, create a **cache/** directory in your app and add the following line to the :file:`dependencyinjection/dicontainer.php`:
.. code-block:: php
<?php
// in the constructor
// if you want to cache the template directory, add this path
$this['TwigTemplateCacheDirectory'] = __DIR__ . '/../cache';
A full reference can be found on the `Twig Template Reference <http://twig.sensiolabs.org/doc/templates.html>`_.
If you want to use Twig together with AngularJS the variable print characters **{{}}** of Angular will have to be adjusted. You can do that by setting a different **$interpolateProvider** in the :file:`coffee/app.coffee` config section:
.. code-block:: js
app.config(['$interpolateProvider', function($interpolateProvider) {
$interpolateProvider.startSymbol('[[');
$interpolateProvider.endSymbol(']]');
}]);
After adding the above lines, Angular will use **[[]]** for evaluation variables.
Additional Twig Extensions
~~~~~~~~~~~~~~~~~~~~~~~~~~
The App Framework comes with additional template functions for Twig to better integrate with ownCloud. The following additional functions are provided:
.. js:function:: url(route, params=null)
:param string route: the name of the route
:param string params: the params written like a JavaScript object
Prints the URL for a route.
An example would be:
.. code-block:: js
{{ url('apptemplate_advanced_params', {value: 'hi'}) }}
.. js:function:: abs_url(route, params=null)
:param string route: the name of the route
:param string params: the params written like a JavaScript object
Same as :js:func:`url` but prints an absolute URL
An example would be:
.. code-block:: js
{{ abs_url('apptemplate_advanced_params', {value: 'hi'}) }}
.. js:function:: trans(toTranslate, params=null)
:param string toTranslate: the string which should be translated
:param string params: the params that should be replaced in the string
Enables translation in the templates
An example would be:
.. code-block:: js
{{ trans('Translate %s %s', 'this', 'and this') }}
ownCloud Templates
------------------
In every template file you can easily access the template functions listed in :doc:`../classes/core/templates`. To access the assigned variables in the template, use the **$_[]** array. The variable will be availabe under the key that you defined (e.g. $_['key']).
:file:`templates/main.php`
.. code-block:: php
<?php foreach($_['entries'] as $entry){ ?>
<p><?php p($entry); ?></p>
<?php
}
print_unescaped($this->inc('sub.inc'));
?>
.. warning::
.. versionchanged:: 5.0
To prevent XSS the following PHP **functions for printing are forbidden: echo, print() and <?=**. Instead use the **p()** function for printing your values. Should you require unescaped printing, **double check for XSS** and use: :php:func:`print_unescaped`.
Templates can also include other templates by using the **$this->inc('templateName')** method. Use this if you find yourself repeating a lot of the same HTML constructs.
The parent variables will also be available in the included templates, but should you require it, you can also pass new variables to it by using the second optional parameter as array for **$this->inc**.
:file:`templates/sub.inc.php`
.. code-block:: php
<div>I am included but i can still access the parents variables!</div>
<?php p($_['name']); ?>
<?php print_unescaped($this->inc('other_template', array('variable' => 'value'))); ?>
**For more info, see** :doc:`../classes/core/templates`

View File

@@ -0,0 +1,138 @@
App Tutorial (App Framework)
============================
.. sectionauthor:: Bernhard Posselt <nukeawhale@gmail.com>
This tutorial contains the MVC approach to write an app. The goal of this tutorial is a simple notes app.
Create an app entry
-------------------
Depending on the . This file will always loaded for every app and can for instance be used to load additional JavaScript for the files app:
:file:`appinfo/app.php`
.. code-block:: php
<?php
namespace OCA\MyNotes;
$api = new \OCA\AppFramework\Core\API('mynotes');
$api->addNavigationEntry(array(
// the string under which your app will be referenced in owncloud
'id' => $api->getAppName(),
// sorting weight for the navigation. The higher the number, the higher
// will it be listed in the navigation
'order' => 10,
// the route that will be shown on startup
'href' => $api->linkToRoute('mynotes_index'),
// the icon that will be shown in the navigation
'icon' => $api->imagePath('example.png' ),
// the title of your application. This will be used in the
// navigation or on the settings page of your app
'name' => $api->getTrans()->t('My notes app')
));
First Page
----------
Now that the basic files are created, the following things are needed to create a page:
* **A route**: The URL which links to the controller
* **A controller**: Gets the request and returns a response
* **An entry in the DIContainer**: This makes the controller available for the application
* **A template**: HTML which should be displayed on the page
First the route which is linked in the :file:`appinfo/app.php` needs to be created. To do that create the :doc:`routes` file:
:file:`appinfo/routes.php`
.. code-block:: php
<?php
namespace OCA\MyNotes;
use \OCA\AppFramework\App;
use \OCA\MyNotes\DependencyInjection\DIContainer;
$this->create('mynotes_index', '/')->action(
function($params){
// call the index method on the class PageController
App::main('PageController', 'index', $params, new DIContainer());
}
);
The :doc:`controllers` to which the route links does not exist yet and it has to be created:
:file:`controllers/pagecontroller.php`
.. code-block:: php
<?php
namespace OCA\MyNotes\Controller;
use OCA\AppFramework\Controller\Controller;
class PageController extends Controller {
public function __construct($api, $request){
parent::__construct($api, $request);
}
/**
* @CSRFExemption
* @IsAdminExemption
* @IsSubAdminExemption
*/
public function index(){
return $this->render('main');
}
}
Now create the template:
:file:`templates/main.php`
.. code-block:: html
<div>Hello World</div>
The last thing that is left is to tell the application how the controller needs to be created. The App Framework makes heavy use of :doc:`../general/dependencyinjection` and provides an IOC Container. Inside this container, the controller needs to be created:
:file:`dependencyinjection/dicontainer.php`
.. code-block:: php
<?php
class DIContainer extends BaseContainer {
public function __construct(){
parent::__construct('mynotes');
// use this to specify the template directory
$this['TwigTemplateDirectory'] = __DIR__ . '/../templates';
$this['PageController'] = function($c){
return new PageController($c['API'], $c['Request']);
};
}
}

View File

@@ -0,0 +1,81 @@
Unittests
=========
.. sectionauthor:: Bernhard Posselt <nukeawhale@gmail.com>
.. note:: App Unittests should **not depend on a running ownCloud instance**! They should be able to run in isolation. To achieve that, abstract the ownCloud core functions and static methods in the App Framework :file:`core/api.php` and use a mock for testing. If a class is not static, you can simply add it in the :file:`dependencyinjection/dicontainer.php`
.. note:: Also use your app's namespace in your test classes to avoid possible conflicts when the test is run on the buildserver
Unittests go into your **tests/** directory. Create the same folder structure in the tests directory like on your app to make it easier to find tests for certain classes.
Owncloud uses `PHPUnit <http://www.phpunit.de/manual/current/en/>`_
Because of Dependency Injection, unittesting has become very easy: you can easily substitute complex classes with `mocks <http://www.phpunit.de/manual/3.0/en/mock-objects.html>`_ by simply passing a different object to the constructor.
Also using a container like `Pimple <http://pimple.sensiolabs.org/>`_ frees us from doing complex instantiation and object passing in our application by hand.
A simple test for a controller would look like this:
:file:`tests/controllers/ItemControllerTest.php`
.. code-block:: php
<?php
namespace OCA\AppTemplateAdvanced;
use OCA\AppFramework\Http\Request;
use OCA\AppFramework\Db\DoesNotExistException;
use OCA\AppFramework\Utility\ControllerTestUtility;
require_once(__DIR__ . "/../classloader.php");
class ItemControllerTest extends ControllerTestUtility {
public function testSetSystemValue(){
$post = array('somesetting' => 'this is a test');
$request = new Request(array(), $post);
// create an api mock object
$api = $this->getAPIMock();
// expects to be called once with the method
// setAppValue('somesetting', 'this is a test')
$api->expects($this->once())
->method('setAppValue')
->with( $this->equalTo('somesetting'),
$this->equalTo('this is a test'));
// we want to return the appname apptemplateadvanced when this method
// is being called
$api->expects($this->any())
->method('getAppName')
->will($this->returnValue('apptemplateadvanced'));
$controller = new ItemController($api, $request, null);
$response = $controller->setAppValue(null);
// check if the correct parameters of the json response are set
$this->assertEquals($post, $response->getParams());
}
}
You can now execute the test by running this in your app directory::
phpunit tests/
.. note:: PHPUnit executes all PHP Files that end with **Test.php**. Be sure to consider that in your file naming.
The Advanced Apptemplate provides an extra classloader :file:`tests/classloader.php` that loads the the classes. Require this file at the top of your tests.
.. note:: The classloader in the **tests/** directory assumes that the **appframework/** folder is in the same directory as the your app. If you run your app in a different apps folder, you will need to link the App Framework into the same folder where your app folder resides.
More examples for testing controllers are in the :file:`tests/controller/ItemControllerTest.php`