Einführung in
Philipp Marien & Alexander Bogomolov
Symfony is a set of reusable PHP components...
... and a High Performance PHP framework for web projects
Symfony Components are a set of decoupled and reusable PHP libraries.
A bridge is a set of classes that aims at extending a library into Symfony2.
A bundle is a directory that has a well-defined structure and can host anything from classes to controllers and web resources.
I (Fabien Potencier) don't like MVC because that's not how the web works. Symfony2 is an HTTP framework; it is a Request/Response framework.
app/
config/
config.yml
parameters.yml
routing.yml
security.yml
AppKernel.php
bin/
console
src/
AppBundle/ # Default Bundle, nur für sehr kleine Anwendungen
Bundles/ # Eigene Bundles
Components/ # Eigene Components
Bridges/ # Eigene Bridges
var/
logs/
cache/
vendor/
web/
app.php
app_dev.php
composer.json
MyBundle/
Controller/
MyController.php
DependencyInjection/
Configuration.php
MyExtension.php
Entity/
MyEntity.php
Form/
MyFormType.php
Repository/
MyEntityRepository.php
Resources/
config/
services.yml
routing.yml
view/
Tests/
MyBundle.php
export SENSIOLABS_ENABLE_NEW_DIRECTORY_STRUCTURE=true
... in die Datei ~/.bashrc oder ~/.bash_profile
composer create-project symfony/framework-standard-edition workshop
# app/config/parameters.yml
parameters:
database_host: 127.0.0.1
database_port: null
database_name: symfony
database_user: root
database_password: null
mailer_transport: smtp
mailer_host: 127.0.0.1
mailer_user: null
mailer_password: null
secret: 808a1411b663ad30dbbb5fab88137f17823dac3b
Teil 1
The core projects are a Object Relational Mapper (ORM) and the Database Abstraction Layer (DBAL) it is built upon.
Code | Datenbank |
---|---|
Klasse (Entität) | Tabelle |
Objekt | Tabellenzeile |
Attribut | Tabellenspalte |
Identität eines Objekts | Primärschlüssel |
Referenzen auf andere Objekte | Fremdschlüssel |
<?php
namespace AppBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* User
*
* @ORM\Table(name="app_user")
* @ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
*/
class User {
/**
* @var integer
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name="email", type="string", length=255, unique=true)
*/
private $email;
/**
* @var string
*
* @ORM\Column(name="password", type="string", length=255)
*/
private $password;
/**
* @var string
*
* @ORM\Column(name="salt", type="string", length=255, nullable=true)
*/
private $salt;
/**
* @var string
*
* @ORM\Column(name="first_name", type="string", length=255)
*/
private $firstName;
/**
* @var string
*
* @ORM\Column(name="last_name", type="string", length=255)
*/
private $lastName;
/**
* @var ArrayCollection
*
* @ORM\OneToMany(targetEntity="AppBundle\Entity\Address", mappedBy="user")
*/
private $addresses;
publich function __construct(){
$this->addresses = new ArrayCollection();
}
// Getter & setter
}
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Table(name="app_user")
* @ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
*/
class User {
// ...
}
/**
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\Column(name="email", type="string", length=255, unique=true,nullable=false)
*/
protected $email;
Ein Nutzer hat mehrere Adressen...
... viele Adressen haben einen Nutzer
/**
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\User", inversedBy="addresses")
* @ORM\JoinColumn(name="user_id")
*/
protected $user;
AppBundle:User
/**
* @ORM\OneToMany(targetEntity="AppBundle\Entity\Address", mappedBy="user")
*/
protected $addresses;
... enthalten die Logik um ein oder mehrere Objekte aus der Datenbank zu laden.
Standard-Methoden:
extends EntityRepository
<?php
namespace AppBundle\Repository;
use Doctrine\ORM\EntityRepository;
/**
* UserRepository
*/
class UserRepository extends EntityRepository
{
public function findAllUserWithDomain($domain) {
return $this->getEntityManager()->createQuery(
'SELECT u FROM AppBundle:User u WHERE u.email LIKE :domain ORDER BY u.firstName ASC'
)
->setParameter('domain', '%' . $domain)
->getResult();
}
}
bin/console doctrine:database:create
bin/console doctrine:database:drop
bin/console doctrine:schema:validate
bin/console doctrine:schema:create
bin/console doctrine:schema:update
Teil 1
Es geht auch einfacher...
bin/console doctrine:generate:entity
Teil 2
The Security component provides a complete security system for your web application.
It ships with facilities for authenticating using HTTP basic or [...] interactive form login [...], but also allows you to implement your own authentication strategies.
[...] the component provides ways to authorize authenticated users based on their roles, and it contains an advanced ACL system.
Woher kommen die Nutzer?
providers:
in_memory:
memory:
users:
ryan:
password: test56
roles: 'ROLE_USER'
admin:
password: test56
roles: 'ROLE_ADMIN'
database:
entity:
class: AppBundle\Entity\User
property: email
webservice:
id: my_webservice_manager
# ...
Wie authentifizieren sich die Nutzer?
Dürfen anonyme Nutzer die Seite aufrufen?
firewalls:
not_secured:
pattern: ^/test$
security: false
anonym:
anonymous: ~
form_login:
login_path: login
check_path: login_check
default:
anonymous: false
http_basic: ~
# ...
Wie werden die Passwörter der Nutzer verschlüsselt?
encoders:
Symfony\Component\Security\Core\User\User: plaintext
AppBundle\Entity\User: sha512
# ...
und
class HelloController extends Controller
{
public function helloAction()
{
return new Response('Hello world!');
}
public function helloNameAction($name)
{
return new Response('Hello ' . $name . '!');
}
}
# app/config/routing.yml
app:
resource: "@AppBundle/Resources/config/routing.yml"
root:
path: /
defaults:
_controller: FrameworkBundle:Redirect:urlRedirect
path: /blog
# AppBundle/Resources/config/routing.yml
blog_post_show:
path: /blog/{slug}
methods: GET
defaults: { _controller: AppBundle:Blog:show }
blog_post_update:
path: /blog/update/{id}
methods: GET|POST
defaults: { _controller: AppBundle:Blog:update }
requirements:
id: \d+
src/
MyBundle/
...
Controller/
TestController.php
Resources/
public/
assets/
views/
Test/
index.html.twig
layout.html.twig
Twig is a modern template engine for PHP
{% tag parameters %}{% endtag %}
{{ variable|filter(parameters) }}
{{ function(parameters) }}
{% if variable is test %}{% endif %}
{{ function(parameters) }}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}Welcome!{% endblock %}</title>
{% block stylesheets %}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" media="all"/>
{% endblock %}
<link rel="icon" type="image/x-icon" href="{{ asset('favicon.ico') }}"/>
</head>
<body>
<main class="container">
{% include '::_navigation.html.twig' %}
{% block body %}{% endblock %}
</main>
{% block javascripts %}
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
{% endblock %}
</body>
</html>
{% extends 'MyBundle::layout.html.twig' %}
{% block title %}
{{ title }}
{% endblock %}
{% block body %}
<h1>Hallo Welt!</h1>
<p>Lorem impsum...</p>
{% endblock %}
class TestController extends Controller
{
public function indexAction()
{
return $this->render('AppBundle:Test:index.html.twig', ['title' => 'Hallo Welt']);
}
public function layoutAction()
{
return $this->render('AppBundle::layout.html.twig', []);
}
}
$em = $this->getDoctrine()->getManager();
create
$user = new User();
$em->persist($user);
$em->flush();
update
$user->setFirstName('Test');
$em->flush();
delete
$em->remove($user);
$em->flush();
$repository = $this->getDoctrine()->getRepository('AppBundle:User');
$user = $repository->find($id);
$user = $repository->findOneBy(['firstName'=>'Test']);
$users = $repository->findAll();
$users = $repository->findBy(['lastName'=>'User'], ['firstName' => 'ASC']);
$users = $repository->findByCustomQuery();
class ContentType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title')
->add('body')
->add('save', 'submit', array('label' => 'Create Content'))
;
}
public function getName()
{
return 'content';
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'MyBundle\Entity\Content',
));
}
}
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
class DefaultController extends Controller
{
public function newAction(Request $request)
{
$form = $this->createForm(new ContentType(), new Content());
$form->handleRequest($request);
if ($form->isValid()) {
//...
return $this->redirectToRoute('success');
}
return $this->render('default/new.html.twig', array(
'form' => $form->createView()
));
}
}
class HelloController extends Controller
{
public function sendEmailAction()
{
$mailer = new Mailer('sendmail');
$mailer->send('foo@bar.net', 'Hello World!');
// ...
}
}
class HelloController extends Controller
{
public function sendEmailAction()
{
$mailer = $this->get('enm_mailer');
$mailer->send('foo@bar.net', 'Hello World!');
// ...
}
}
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="enm_mailer" class="EnmHelloBundleMailer">
<argument>sendmail</argument>
</service>
</services>
</container>
<parameters>
<parameter key="enm_mailer.transport">sendmail</parameter>
</parameters>
<services>
<service id="enm_mailer" class="EnmHelloBundleMailer">
<argument>%enm_mailer.transport%</argument>
</service>
</services>
class AppExtension extends Extension {
public function load(array $config, ContainerBuilder $container)
{
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.xml');
}
}
# app/config/parameters.yml
parameters:
mailer.transport: sendmail
$container->hasParameter('mailer.transport');
$container->getParameter('mailer.transport');
$container->setParameter('mailer.transport', 'sendmail');
In the service container, a tag implies that the service is meant to be used for a specific purpose
<services>
<service id="foo.twig.extension" class="EnmHelloBundleExtensionFooExtension" public="false">
<tag name="twig.extension"/>
</service>
</services>
The Doctrine database abstraction & access layer (DBAL) offers a lightweight and thin runtime layer arounda PDO-like API and a lot of additional, horizontal features like database schema introspection and manipulation through an OO API.
<?php
namespace AppBundle\Repository;
use Doctrine\ORM\EntityRepository;
/**
* UserRepository
*/
class UserRepository extends EntityRepository
{
public function findAllUserWithDomain($domain) {
return $this->createQueryBuilder('u')
->where('u.email LIKE :domain')
->orderBy('u.firstName', 'ASC')
->setParameter('domain', '%' . $domain)
->getQuery()
->getResult();
}
}
The EventDispatcher component provides tools that allow your application components to communicate with each other by dispatching events and listening to them.
Beim Anlegen eines neuen Benutzers soll eine Bestätigungs-E-Mail an diesen verschickt werden.
class UserEvent extends Event {
protected $user;
public function __construct(User $user) {
$this->user = $user;
}
public function getUser() {
return $this->user;
}
}
class UserController extends Controller {
public function userSaveAction(Request $request) {
// [...]
$doctrine->persist($user);
$doctrine->flush();
$dispatcher = $this->get('event_dispatcher');
$dispatcher->dispatch('myapp.user_insert', new UserEvent($user));
// [...]
return ['user' => $user];
}
}
namespace MyBundle\EventListener;
class UserEventListener {
protected $mailer;
public function __construct(\Swift_Mailer $mailer) {
$this->mailer = $mailer;
}
public function onUserInsert(UserEvent $event) {
$message = \Swift_Message::newInstance()->setTo($event->getUser()->getEMail());
// [...]
$this->mailer->send($message);
}
}
<service id="kernel.listener.user_insert_listener"
class="MyBundleEventListenerUserEventListener">
<argument type="service" id="mailer"/>
<tag name="kernel.event_listener" event="myapp.user_insert" method="onUserInsert"/>
</service>
The Console component eases the creation of beautiful and testable command line interfaces.
bin/console enm:mail marien@eosnewmedia.de "Hallo!"
class MailCommand extends Command
{
protected function configure()
{
$this->setName('enm:mail');
$this->addArgument('to', InputArgument::REQUIRED, 'Who should receive your email?');
$this->addArgument('text', InputArgument::REQUIRED, 'Your email text...');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$to = $this->getArgument('to');
$text = $this->getArgument('text');
// [...]
$output->writeln('E-Mail has been sent.');
}
}