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

@@ -1,3 +1,33 @@
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
=====================

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

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

View File

@@ -1,5 +1,5 @@
Create An App
=============
Creating An App
===============
.. sectionauthor:: Bernhard Posselt <nukeawhale@gmail.com>
@@ -71,8 +71,12 @@ Start coding
------------
The basic files are now in place and the app is enabled. There are two ways to create the app:
* :doc:`Use ownClouds app API <tutorial>`
* :doc:`Use the App Framework app <../appframework/tutorial>`
* Use the :doc:`ownCloud app API <../app/tutorial>`
* Use the :doc:`App Framework app <../appframework/tutorial>`
If you are new to programming and want to create an app fast you migth want to use the ownCloud app API, if you are an advanced programmer or used to frameworks you might want to use the App Framework App.
To simplify the decision see this comparison chart:
+-----------------+-------------------------+--------------------------------+
| Criteria | ownCloud app API | App Framework |
@@ -85,7 +89,7 @@ The basic files are now in place and the app is enabled. There are two ways to c
| | | ../general/dependencyinjection`|
| | | and `TDD`_ tools |
+-----------------+-------------------------+--------------------------------+
| Maintainability | hard | medium |
| Maintainability | hard | easy |
+-----------------+-------------------------+--------------------------------+
| Templates | :php:class:`OC_Template`| :php:class:`OC_Template` |
| | | and `Twig`_ |

View File

@@ -0,0 +1,21 @@
.. _contents:
================
App Developement
================
.. toctree::
:maxdepth: 1
tutorial
info
classloader
schema
database
static
css
javascript
externalapi
filesystem
hooks
data-migration

View File

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

View File

@@ -3,257 +3,30 @@ Database Access
.. sectionauthor:: Bernhard Posselt <nukeawhale@gmail.com>
.. note:: This will likely change with the introduction of an ORM
ownCloud uses a database abstraction layer on top of either MDB2 or PDO, depending on the availability of PDO on the server.
Your database schema will be inside :file:`appinfo/database.xml` in MDB2's `XML scheme notation <http://www.sulc.edu/sulcalumni/app/lib/pear/docs/MDB2_Schema/docs/xml_schema_documentation.html>`_ where the placeholders \*dbprefix* (\*PREFIX* in your SQL) and \*dbname* can be used for the configured database table prefix and database name.
An example database XML file would look like this:
.. code-block:: xml
<?xml version="1.0" encoding="ISO-8859-1" ?>
<database>
<name>*dbname*</name>
<create>true</create>
<overwrite>false</overwrite>
<charset>utf8</charset>
<table>
<name>*dbprefix*yourapp_items</name>
<declaration>
<field>
<name>id</name>
<type>integer</type>
<default>0</default>
<notnull>true</notnull>
<autoincrement>1</autoincrement>
<length>4</length>
</field>
<field>
<name>user</name>
<type>text</type>
<notnull>true</notnull>
<length>64</length>
</field>
<field>
<name>name</name>
<type>text</type>
<notnull>true</notnull>
<length>100</length>
</field>
<field>
<name>path</name>
<type>clob</type>
<notnull>true</notnull>
</field>
</declaration>
</table>
</database>
To update the tables used by the app, simply adjust the database.xml file and increase the app version number in :file:`appinfo/version` to trigger an update.
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`
After the schema has been defined it is possible to query the database. ownCloud uses prepared statements. A simple query would look like this:
.. code-block:: php
<?php
namespace \OCA\YourApp\Db;
// *PREFIX* is being replaced with the ownCloud installation prefix
$sql = 'SELECT * FROM `*PREFIX*myusers` WHERE id = ?';
$args = array(1);
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;
}
$query = \OCP\DB::prepare($sql);
$result = $query->execute($args);
while($row = $result->fetchRow()) {
$userName = $row['username'];
}
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`
If a new element is saved to the database the inserted id can be accessed by using:
.. code-block:: php
<?php
namespace \OCA\YourApp\Db;
use \OCA\AppFramework\Db\DoesNotExistException;
use \OCA\AppFramework\Db\Mapper;
$id = \OCP\DB::insertid();
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);
For more information about MDB2 style prepared statements, please see the `official MDB2 documentation <http://pear.php.net/package/MDB2/docs>`_
It is also possible to use transactions. To start a transaction use **

View File

@@ -1,14 +1,49 @@
.. _index:
================
App Developement
================
.. toctree::
:maxdepth: 1
.. _Junior Jobs: http://owncloud.org/dev/junior-jobs/
tutorial
classloader
info
externalapi
filesystem
hooks
data-migration
.. _git crash course: http://git-scm.com/course/svn.html
.. _Twig Templates: http://twig.sensiolabs.org/
.. _Symfony Routing: http://symfony.com/doc/current/components/routing/introduction.html
.. _Pimple: http://pimple.sensiolabs.org/
.. _PHPUnit: http://www.phpunit.de/manual/current/en/
* Take a job from our `Junior Jobs`_
* :doc:`general/security`
* :doc:`general/codingguidelines`
* :doc:`general/codereviews`
* :doc:`general/debugging`
* :doc:`general/kanban`
* :doc:`general/angular` | `AngularJS Documentation <http://angularjs.org/>`_
* :doc:`general/dependencyinjection` | `Pimple`_
App Developement
================
* :doc:`appintro/gettingstarted`
* :doc:`appintro/createapp`
Tutorial
--------
You can choose between the traditional and MVC style (App Framework) approach.
* :doc:`app/tutorial`
* :doc:`appframework/tutorial`
App Framework
-------------
API Documentation
=================
* :doc:`classes/appframework/index`
* :doc:`classes/core/index`

View File

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

View File

@@ -0,0 +1,52 @@
Database Schema
===============
.. sectionauthor:: Bernhard Posselt <nukeawhale@gmail.com>
ownCloud uses a database abstraction layer on top of either MDB2 or PDO, depending on the availability of PDO on the server.
The database schema is inside :file:`appinfo/database.xml` in MDB2's `XML scheme notation <http://www.wiltonhotel.com/_ext/pear/docs/MDB2/docs/xml_schema_documentation.html>`_ where the placeholders \*dbprefix* (\*PREFIX* in your SQL) and \*dbname* can be used for the configured database table prefix and database name.
An example database XML file would look like this:
.. code-block:: xml
<?xml version="1.0" encoding="ISO-8859-1" ?>
<database>
<name>*dbname*</name>
<create>true</create>
<overwrite>false</overwrite>
<charset>utf8</charset>
<table>
<name>*dbprefix*yourapp_items</name>
<declaration>
<field>
<name>id</name>
<type>integer</type>
<default>0</default>
<notnull>true</notnull>
<autoincrement>1</autoincrement>
<length>4</length>
</field>
<field>
<name>user</name>
<type>text</type>
<notnull>true</notnull>
<length>64</length>
</field>
<field>
<name>name</name>
<type>text</type>
<notnull>true</notnull>
<length>100</length>
</field>
<field>
<name>path</name>
<type>clob</type>
<notnull>true</notnull>
</field>
</declaration>
</table>
</database>
To update the tables used by the app, simply adjust the database.xml file and increase the app version number in :file:`appinfo/version` to trigger an update.

View File

@@ -1,9 +1,17 @@
JavaScript and CSS
==================
Static content
==============
.. sectionauthor:: Bernhard Posselt <nukeawhale@gmail.com>
Static content is compressed by ownCloud and therefore needs to be added in the controller. If the CSS or JavaScript does not seem to get updated, check if the debug mode is enabled. To enable it see :doc:`gettingstarted`
Static content consists of:
* **img/**: all images
* **js/**: all JavaScript files
* **css/**: all CSS files
.. note:: CSS and JavaScript are compressed by ownCloud so if the CSS or JavaScript do not seem to get updated, check if the debug mode is enabled. To enable it see :doc:`../appintro/gettingstarted`
JavaScript and CSS
------------------

View File

@@ -1,27 +0,0 @@
Dependency Injection Container
==============================
.. 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`