composer create-project symfony/skeleton symfony4demo 4.0.0-BETA1
composer require webserver
Then run the server
php bin\console server:run
Visit the page at http://localhost:8000
Create the controller in src/controller
.
Make sure to have the correct namespace and return a response.
<?php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
class HelloWorld
{
public function index()
{
return new Response("Hello World");
}
}
Add a route to point to the controller in config/routes.yml
index:
path: /
defaults: { _controller: 'App\Controller\HelloWorld::index' }
composer require annotation
Add use statement and docblock to the controller method.
<?php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class HelloWorld
{
/**
* @Route("/", name="hello world")
* @return Response
*/
public function index()
{
return new Response("Hello World");
}
}
Modify the route config file to load annotated routes from your controllers
controllers:
resource: ../src/controller/
type: annotation
/**
* @Route("/hello/{name}", name="hello robot", requirements={"name": "\d+"})
* @return Response
*/
public function robot($name)
{
return new Response(sprintf("Hello robot: %s.", $name));
}
/**
* @Route("/hello/{name}", name="hello human")
* @return Response
*/
public function human($name = "john doe")
{
return new Response(sprintf("Hello human: %s.", $name));
}
install the profiler
composer require profiler
Go to http://localhost:8000/_profiler to view information on recent request
The profiler depends on Twig so the Templates directory and a base template file have been made for us thanks to Symfony Flex.
Modify the base file for some basic styling
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{% block title %}Welcome!{% endblock %}</title>
{% block stylesheets %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/foundation/6.2.4/foundation.min.css">
{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
{% block javascripts %}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/foundation/6.2.4/foundation.min.js"></script>
<script>
jQuery(function() {
jQuery(document).foundation();
});
</script>
{% endblock %}
</body>
</html>
Create a basic page template named page.html.twig
in the template directory that extends base
{% extends 'base.html.twig' %}
{% block body %}
<div class="row align-center">
<div class="small-12 medium-10 large-8 columns">
{{ content | raw }}
</div>
</div>
{% endblock %}
Replace the controller methods to use Twig
<?php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class HelloWorld
{
/**
* @Route("/", name="hello world")
* @return Response
*/
public function index(\Twig_Environment $twig)
{
$data = [
'title' => 'Hello World',
'content' => '<h1>Hello World</h1>',
];
return new Response($twig->render('page.html.twig', $data));
}
/**
* @Route("/hello/{name}", name="hello robot", requirements={"name": "\d+"})
* @return Response
*/
public function robot(\Twig_Environment $twig, $name)
{
$data = [
'title' => 'Hello Robot',
'content' => '<h1>'.sprintf("Hello %s.", $name).'</h1>',
];
return new Response($twig->render('page.html.twig', $data));
}
/**
* @Route("/hello/{name}", name="hello human")
* @return Response
*/
public function human(\Twig_Environment $twig, $name = "john doe")
{
$data = [
'title' => 'Hello Human',
'content' => '<h1>'.sprintf("Hello %s.", $name).'</h1>',
];
return new Response($twig->render('page.html.twig', $data));
}
}
Note that when viewing pages, you know get a profiler toolbar.
Install doctrine
composer require doctrine
configure the env file with the database connection
DATABASE_URL="mysql://root:@127.0.0.1:3306/symfony4demo?charset=utf8mb4&serverVersion=5.7"
Create the entity class
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Table(name="posts")
* @ORM\Entity(repositoryClass="App\Repository\PostRepository")
*/
class Post
{
/**
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\Column(type="integer")
*/
private $author;
/**
* @ORM\Column(type="datetime", nullable=true)
*/
private $published_on;
/**
* @ORM\Column(type="datetime")
*/
private $last_modified_on;
/**
* @ORM\Column(type="string", length=80, unique=true)
*/
private $slug;
/**
* @ORM\Column(type="string", length=80)
*/
private $title;
/**
* @ORM\Column(type="text")
*/
private $content;
/**
* @ORM\Column(type="boolean")
*/
private $is_restricted;
/**
* @return mixed
*/
public function getId()
{
return $this->id;
}
/**
* @param mixed $id
* @return Post
*/
public function setId($id)
{
$this->id = $id;
return $this;
}
/**
* @return mixed
*/
public function getAuthor()
{
return $this->author;
}
/**
* @param mixed $author
* @return Post
*/
public function setAuthor($author)
{
$this->author = $author;
return $this;
}
/**
* @return mixed
*/
public function getPublishedOn()
{
return $this->published_on;
}
/**
* @param mixed $published_on
* @return Post
*/
public function setPublishedOn($published_on)
{
$this->published_on = $published_on;
return $this;
}
/**
* @return mixed
*/
public function getLastModifiedOn()
{
return $this->last_modified_on;
}
/**
* @param mixed $last_modified_on
* @return Post
*/
public function setLastModifiedOn($last_modified_on)
{
$this->last_modified_on = $last_modified_on;
return $this;
}
/**
* @return mixed
*/
public function getSlug()
{
return $this->slug;
}
/**
* @param mixed $slug
* @return Post
*/
public function setSlug($slug)
{
$this->slug = $slug;
return $this;
}
/**
* @return mixed
*/
public function getTitle()
{
return $this->title;
}
/**
* @param mixed $title
* @return Post
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* @return mixed
*/
public function getContent()
{
return $this->content;
}
/**
* @param mixed $content
* @return Post
*/
public function setContent($content)
{
$this->content = $content;
return $this;
}
/**
* @return mixed
*/
public function getisRestricted()
{
return $this->is_restricted;
}
/**
* @param mixed $is_restricted
* @return Post
*/
public function setIsRestricted($is_restricted)
{
$this->is_restricted = $is_restricted;
return $this;
}
}
Create the Repository class
<?php
namespace App\Repository;
use Doctrine\ORM\EntityRepository;
class PostRepository extends EntityRepository
{
}
Create the Route. Preferable in a new Controller.
/**
* @Route("blog/{slug}", name="blog view", requirements={"slug":"[a-zA-Z0-9\-]+"})
* @param string $slug
*/
public function view(string $slug, ManagerRegistry $doctrine, \Twig_Environment $twig)
{
/** @var $post \App\Entity\Post */
$post = $doctrine->getRepository(\App\Entity\Post::class)
->findOneBy(['slug' => $slug]);
if(!$post) {
return new NotFoundHttpException();
}
$data = [
'title' => $post->getTitle(),
'content' => $post->getContent(),
];
return new Response($twig->render('page.html.twig', $data));
}
Create tag entity
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Table(name="tags")
* @ORM\Entity(repositoryClass="App\Repository\TagRepository")
*/
class Tag
{
/**
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\Column(type="string", length=80, unique=true)
*/
private $name;
/**
* @ORM\Column(type="text", nullable=true)
*/
private $description;
/**
* @return mixed
*/
public function getId()
{
return $this->id;
}
/**
* @param mixed $id
* @return Tag
*/
public function setId($id)
{
$this->id = $id;
return $this;
}
/**
* @return mixed
*/
public function getName()
{
return $this->name;
}
/**
* @param mixed $name
* @return Tag
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* @return mixed
*/
public function getDescription()
{
return $this->description;
}
/**
* @param mixed $description
* @return Tag
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
}
Create the tag repository
<?php
namespace App\Repository;
use Doctrine\ORM\EntityRepository;
class TagRepository extends EntityRepository
{
}
add the relationship to the post entity
/**
* @ORM\ManyToMany(targetEntity="Tag")
* @ORM\JoinTable(name="post_tags",
* joinColumns={@ORM\JoinColumn(name="post_id", referencedColumnName="id")},
* inverseJoinColumns={@ORM\JoinColumn(name="tag_id", referencedColumnName="id")}
* )
*/
private $tags;
public function __construct() {
$this->tags = new ArrayCollection();
}
Have the controller pass the object instead of passing every property
/**
* @Route("blog/{slug}", name="blog view", requirements={"slug":"[a-zA-Z0-9\-]+"})
* @param string $slug
*/
public function view(string $slug, ManagerRegistry $doctrine, \Twig_Environment $twig)
{
/** @var $post \App\Entity\Post */
$post = $doctrine->getRepository(\App\Entity\Post::class)
->findOneBy(['slug' => $slug]);
if(!$post) {
return new NotFoundHttpException();
}
$data = [
'title' => $post->getTitle(),
'post' => $post,
];
return new Response($twig->render('post.html.twig', $data));
}
Create a new template just for post
{% extends 'base.html.twig' %}
{% block body %}
<div class="row align-center">
<div class="small-12 medium-10 large-8 columns">
<h1>{{ post.title }}</h1>
{{ post.content | raw }}
{% for tag in post.tags %}
<a class="hollow button">{{ tag.name }}</a>
{% endfor %}
</div>
</div>
{% endblock %}
install
composer require javiereguiluz/easyadmin-bundle
Add configuration
easy_admin:
entities:
Post:
class: App\Entity\Post
Tag:
class: App\Entity\Tag
design:
brand_color: "#4CAF50"
menu:
- { label: "Back to home", route: 'hello world', icon: 'globe' }
- { label: "Entities" }
- { label: "Posts", entity: "Post", icon: "thumb-tack" }
- { label: "Tags", entity: "Tag", icon: "tag" }
Add toString method to tags so they can be displayed when viewing post entities
public function __toString()
{
return $this->getName();
}
view pages at http://localhost:8000/admin
Add users
providers:
in_memory:
memory:
users:
admin:
password: 'password'
roles: 'ROLE_ADMIN'
kitten:
password: 'password'
roles: 'ROLE_USER'
Define roles hierarchy
role_hierarchy:
ROLE_ADMIN: ["ROLE_EDITOR", "ROLE_ALLOWED_TO_SWITCH"]
ROLE_EDITOR: "ROLE_USER"
Restrict section of website to admin role
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
Define the password encoder
encoders:
Symfony\Component\Security\Core\User\User:
algorithm: bcrypt
cost: 12
Get encoded passwords with
php bin/console security:encode-password
Replace plaintext passwords with the encoded ones
providers:
in_memory:
memory:
users:
admin:
password: '$2y$12$d7vhR2iUF5Y40/n5iU521.xgJe3MAqo59Ca2CZ21D0hSVDUy7IV.e'
roles: 'ROLE_ADMIN'
kitten:
password: '$2y$12$7KDd0QL6NtPFrXjyAzXDzO1ljctYlxB2moTiC9gt7iz11IXG4kj/e'
roles: 'ROLE_USER'
Set the firewall to use a form
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: ~
form_login:
login_path: login
check_path: login
Enable sessions in the framework config
session: ~
Define the controller.
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class Security extends Controller
{
/**
* @Route("/login", name="login")
*/
public function loginAction(Request $request, AuthenticationUtils $authUtils)
{
$error = $authUtils->getLastAuthenticationError();
$lastUsername = $authUtils->getLastUsername();
return $this->render('login.html.twig', [
'last_username' => $lastUsername,
'error' => $error,
]);
}
}
Add the template
{% extends 'base.html.twig' %}
{% block body %}
<div class="row align-center">
<div class="small-12 medium-10 large-8 columns">
<h1>Login</h1>
{% if error %}
<div class="alert callout"><p>{{ error.messageKey|trans(error.messageData, 'security') }}</p></div>
{% endif %}
<form action="{{ path('login') }}" method="post">
<div class="row">
<div class="small-6 columns">
<label for="username">Username
<input type="text" id="username" name="_username" value="{{ last_username }}" />
</label>
</div>
<div class="small-6 columns">
<label for="password">Password
<input type="password" id="password" name="_password" />
</label>
</div>
</div>
<button type="submit" class="primary button">login</button>
</form>
</div>
</div>
{% endblock %}