.. index:: single: Propel
Let's face it, one of the most common and challenging tasks for any application involves persisting and reading information to and from a database. Symfony2 does not come integrated with any ORMs but the Propel integration is easy. To get started, read Working With Symfony2.
In this section, you'll configure your database, create a Product
object,
persist it to the database and fetch it back out.
Code along with the example
If you want to follow along with the example in this chapter, create an
AcmeStoreBundle
via:
$ php app/console generate:bundle --namespace=Acme/StoreBundle
Before you can start, you'll need to configure your database connection
information. By convention, this information is usually configured in an
app/config/parameters.yml
file:
# app/config/parameters.yml
parameters:
database_driver: mysql
database_host: localhost
database_name: test_project
database_user: root
database_password: password
database_charset: UTF8
Note
Defining the configuration via parameters.yml
is just a convention. The
parameters defined in that file are referenced by the main configuration
file when setting up Propel:
propel:
dbal:
driver: "%database_driver%"
user: "%database_user%"
password: "%database_password%"
dsn: "%database_driver%:host=%database_host%;dbname=%database_name%;charset=%database_charset%"
Now that Propel knows about your database, Symfony2 can create the database for you:
$ php app/console propel:database:create
Note
In this example, you have one configured connection, named default
. If
you want to configure more than one connection, read the PropelBundle
configuration section.
In the Propel world, ActiveRecord classes are known as models because classes generated by Propel contain some business logic.
Note
For people who use Symfony2 with Doctrine2, models are equivalent to entities.
Suppose you're building an application where products need to be displayed.
First, create a schema.xml
file inside the Resources/config
directory
of your AcmeStoreBundle
:
<?xml version="1.0" encoding="UTF-8"?>
<database name="default" namespace="Acme\StoreBundle\Model" defaultIdMethod="native">
<table name="product">
<column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" />
<column name="name" type="varchar" primaryString="true" size="100" />
<column name="price" type="decimal" />
<column name="description" type="longvarchar" />
</table>
</database>
After creating your schema.xml
, generate your model from it by running:
$ php app/console propel:model:build
This generates each model class to quickly develop your application in the
Model/
directory the AcmeStoreBundle
bundle.
Now you have a usable Product
class and all you need to persist it. Of
course, you don't yet have the corresponding product
table in your
database. Fortunately, Propel can automatically create all the database tables
needed for every known model in your application. To do this, run:
$ php app/console propel:sql:build
$ php app/console propel:sql:insert --force
Your database now has a fully-functional product
table with columns that
match the schema you've specified.
Tip
You can run the last three commands combined by using the following
command: php app/console propel:build --insert-sql
.
Now that you have a Product
object and corresponding product
table,
you're ready to persist data to the database. From inside a controller, this
is pretty easy. Add the following method to the DefaultController
of the
bundle:
// src/Acme/StoreBundle/Controller/DefaultController.php // ... use Acme\StoreBundle\Model\Product; use Symfony\Component\HttpFoundation\Response; public function createAction() { $product = new Product(); $product->setName('A Foo Bar'); $product->setPrice(19.99); $product->setDescription('Lorem ipsum dolor'); $product->save(); return new Response('Created product id '.$product->getId()); }
In this piece of code, you instantiate and work with the $product
object.
When you call the save()
method on it, you persist it to the database. No
need to use other services, the object knows how to persist itself.
Note
If you're following along with this example, you'll need to create a :doc:`route <routing>` that points to this action to see it in action.
Fetching an object back from the database is even easier. For example, suppose
you've configured a route to display a specific Product
based on its id
value:
// ... use Acme\StoreBundle\Model\ProductQuery; public function showAction($id) { $product = ProductQuery::create() ->findPk($id); if (!$product) { throw $this->createNotFoundException('No product found for id '.$id); } // ... do something, like pass the $product object into a template }
Once you've fetched an object from Propel, updating it is easy. Suppose you have a route that maps a product id to an update action in a controller:
// ... use Acme\StoreBundle\Model\ProductQuery; public function updateAction($id) { $product = ProductQuery::create() ->findPk($id); if (!$product) { throw $this->createNotFoundException('No product found for id '.$id); } $product->setName('New product name!'); $product->save(); return $this->redirect($this->generateUrl('homepage')); }
Updating an object involves just three steps:
- fetching the object from Propel;
- modifying the object;
- saving it.
Deleting an object is very similar, but requires a call to the delete()
method on the object:
$product->delete();
Propel provides generated Query
classes to run both basic and complex queries
without any work:
\Acme\StoreBundle\Model\ProductQuery::create()->findPk($id); \Acme\StoreBundle\Model\ProductQuery::create() ->filterByName('Foo') ->findOne();
Imagine that you want to query for products which cost more than 19.99, ordered from cheapest to most expensive. From inside a controller, do the following:
$products = \Acme\StoreBundle\Model\ProductQuery::create() ->filterByPrice(array('min' => 19.99)) ->orderByPrice() ->find();
In one line, you get your products in a powerful oriented object way. No need to waste your time with SQL or whatever, Symfony2 offers fully object oriented programming and Propel respects the same philosophy by providing an awesome abstraction layer.
If you want to reuse some queries, you can add your own methods to the
ProductQuery
class:
// src/Acme/StoreBundle/Model/ProductQuery.php class ProductQuery extends BaseProductQuery { public function filterByExpensivePrice() { return $this ->filterByPrice(array('min' => 1000)) } }
But note that Propel generates a lot of methods for you and a simple
findAllOrderedByName()
can be written without any effort:
\Acme\StoreBundle\Model\ProductQuery::create() ->orderByName() ->find();
Suppose that the products in your application all belong to exactly one
"category". In this case, you'll need a Category
object and a way to relate
a Product
object to a Category
object.
Start by adding the category
definition in your schema.xml
:
<database name="default" namespace="Acme\StoreBundle\Model" defaultIdMethod="native">
<table name="product">
<column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" />
<column name="name" type="varchar" primaryString="true" size="100" />
<column name="price" type="decimal" />
<column name="description" type="longvarchar" />
<column name="category_id" type="integer" />
<foreign-key foreignTable="category">
<reference local="category_id" foreign="id" />
</foreign-key>
</table>
<table name="category">
<column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" />
<column name="name" type="varchar" primaryString="true" size="100" />
</table>
</database>
Create the classes:
$ php app/console propel:model:build
Assuming you have products in your database, you don't want lose them. Thanks to migrations, Propel will be able to update your database without losing existing data.
$ php app/console propel:migration:generate-diff
$ php app/console propel:migration:migrate
Your database has been updated, you can continue to write your application.
Now, let's see the code in action. Imagine you're inside a controller:
// ... use Acme\StoreBundle\Model\Category; use Acme\StoreBundle\Model\Product; use Symfony\Component\HttpFoundation\Response; class DefaultController extends Controller { public function createProductAction() { $category = new Category(); $category->setName('Main Products'); $product = new Product(); $product->setName('Foo'); $product->setPrice(19.99); // relate this product to the category $product->setCategory($category); // save the whole $product->save(); return new Response( 'Created product id: '.$product->getId().' and category id: '.$category->getId() ); } }
Now, a single row is added to both the category
and product tables. The
product.category_id
column for the new product is set to whatever the id is
of the new category. Propel manages the persistence of this relationship for
you.
When you need to fetch associated objects, your workflow looks just like it did
before. First, fetch a $product
object and then access its related
Category
:
// ... use Acme\StoreBundle\Model\ProductQuery; public function showAction($id) { $product = ProductQuery::create() ->joinWithCategory() ->findPk($id); $categoryName = $product->getCategory()->getName(); // ... }
Note, in the above example, only one query was made.
You will find more information on relations by reading the dedicated chapter on Relationships.
Sometimes, you need to perform an action right before or after an object is inserted, updated, or deleted. These types of actions are known as "lifecycle" callbacks or "hooks", as they're callback methods that you need to execute during different stages of the lifecycle of an object (e.g. the object is inserted, updated, deleted, etc).
To add a hook, just add a new method to the object class:
// src/Acme/StoreBundle/Model/Product.php // ... class Product extends BaseProduct { public function preInsert(\PropelPDO $con = null) { // do something before the object is inserted } }
Propel provides the following hooks:
preInsert()
code executed before insertion of a new objectpostInsert()
code executed after insertion of a new objectpreUpdate()
code executed before update of an existing objectpostUpdate()
code executed after update of an existing objectpreSave()
code executed before saving an object (new or existing)postSave()
code executed after saving an object (new or existing)preDelete()
code executed before deleting an objectpostDelete()
code executed after deleting an object
All bundled behaviors in Propel are working with Symfony2. To get more information about how to use Propel behaviors, look at the Behaviors reference section.
You should read the dedicated section for Propel commands in Symfony2.