Сервисы и бандлы в symfony. введение

Installing Packages¶

A common practice when developing Symfony applications is to install packages
(Symfony calls them bundles) that provide ready-to-use
features. Packages usually require some setup before using them (editing some
file to enable the bundle, creating some file to add some initial config, etc.)

Most of the time this setup can be automated and that’s why Symfony includes
Symfony Flex, a tool to simplify the installation/removal of packages in
Symfony applications. Technically speaking, Symfony Flex is a Composer plugin
that is installed by default when creating a new Symfony application and which
automates the most common tasks of Symfony applications.

Tip

You can also add Symfony Flex to an existing project.

Symfony Flex modifies the behavior of the , , and
Composer commands to provide advanced features. Consider the
following example:

1
2
$ cd my-project/
$ composer require logger

If you execute that command in a Symfony application which doesn’t use Flex,
you’ll see a Composer error explaining that is not a valid package
name. However, if the application has Symfony Flex installed, that command
installs and enables all the packages needed to use the official Symfony logger.

This is possible because lots of Symfony packages/bundles define «recipes»,
which are a set of automated instructions to install and enable packages into
Symfony applications. Flex keeps tracks of the recipes it installed in a
file, which must be committed to your code repository.

Symfony Flex recipes are contributed by the community and they are stored in
two public repositories:

  • Main recipe repository, is a curated list of recipes for high quality and
    maintained packages. Symfony Flex only looks in this repository by default.
  • Contrib recipe repository, contains all the recipes created by the
    community. All of them are guaranteed to work, but their associated packages
    could be unmaintained. Symfony Flex will ask your permission before installing
    any of these recipes.

Read the Symfony Recipes documentation to learn everything about how to
create recipes for your own packages.

Запросы, ответы и веб-разработка¶

Обмен запросами-ответами — это фундаментальный процесс, на котором основывается вся
коммуникация в сети. Этот процесс настолько важен и функционален, что его простота изумляет.

Самое важное заключается в следующем: независимо от того, какой язык программирования вы
используете, какое приложение создаёте (web, мобильное, JSON API) и даже какой философии
программирования придерживаетесь, конечной целью приложения всегда будет понять
запрос, а потом создать и вернуть подходящий ответ. Чтобы узнать больше про спецификацию HTTP, почитайте оригинальную спецификацию HTTP 1.1 RFC
или HTTP Bis, который является попыткой разъяснить исходную спецификацию и, кроме того,
постоянно обновляется.

Чтобы узнать больше про спецификацию HTTP, почитайте оригинальную спецификацию HTTP 1.1 RFC
или HTTP Bis, который является попыткой разъяснить исходную спецификацию и, кроме того,
постоянно обновляется.

Запросы и ответы в PHP

Как же вам обработать «запрос» и создать «ответ» с помощью PHP? На самом деле PHP
немного абстрагирует вас от всего процесса:

1
2
3
4
5
6
$uri = $_SERVER'REQUEST_URI'];
$foo = $_GET'foo'];

header('Content-Type: text/html');
echo 'Запрошенный URI: '.$uri;
echo 'Значение параметра "foo": '.$foo;

Как бы странно это ни звучало, это крохотное приложение на самом деле получает информацию
из HTTP-запроса и использует её для создания HTTP-ответа. Вместо того, чтобы обрабатывать
«сырой» HTTP-запрос, PHP готовит суперглобальные переменные, такие как и ,
которые содержат всю информацию о запросе. Аналогично, вместо того, чтобы возвращать текст
ответа, отформатированный по правилам HTTP, вы можете использовать PHP функцию
для создания заголовков ответов и просто вывести на печать
основной контент, который станет телом ответа. PHP создаст правильный HTTP-ответ и вернет
его клиенту:

1
2
3
4
5
6
7
HTTP/1.1 200 OK
Date: Sat, 03 Apr 2011 02:14:33 GMT
Server: Apache/2.2.17 (Unix)
Content-Type: text/html

Запрошенный URI: /testing?foo=symfony
Значение параметра "foo": symfony

Цели ограничения¶

Ограничения могут быть применены к свойству класса (например, ),
публичному геттер-методу (например, ) или целому классу.
Первый вариант наиболее распространенный и легкий. Однако ограничения геттера
позволяют вам указывать более сложные правила валидации. И, наконец, ограничения
класса предназначены для случаев, когда вы хотите валидировать класс в качестве
единого целого.

Свойства

Валидация свойств класса — самая простая техника валидации. Symfony позволяет
вам проверять приватные, защищенные или публичные свойства. Ниже вы увидите,
как сделать так, чтобы свойство класса имело как минимум
3 символа.

  • Annotations

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    // src/AppBundle/Entity/Author.php
    
    // ...
    use Symfony\Component\Validator\Constraints as Assert;
    
    class Author
    {
        /**
         * @Assert\NotBlank()
         * @Assert\Length(min=3)
         */
        private $firstName;
    }
    
  • YAML

    1
    2
    3
    4
    5
    6
    7
    # src/AppBundle/Resources/config/validation.yml
    AppBundle\Entity\Author
        properties
            firstName
                - NotBlank ~
                - Length
                    min 3
    
  • XML

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    <?xml version="1.0" encoding="UTF-8" ?>
    xmlns="http://symfony.com/schema/dic/constraint-mapping"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping
            http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
    
        name="AppBundle\Entity\Author">
            name="firstName">
                name="NotBlank" />
                name="Length">
                    name="min">3
    
                
            
        
    
    
  • PHP

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // src/AppBundle/Entity/Author.php
    
    // ...
    use Symfony\Component\Validator\Mapping\ClassMetadata;
    use Symfony\Component\Validator\Constraints as Assert;
    
    class Author
    {
        private $firstName;
    
        public static function loadValidatorMetadata(ClassMetadata $metadata)
        {
            $metadata->addPropertyConstraint('firstName', new Assert\NotBlank());
            $metadata->addPropertyConstraint(
                'firstName',
                new Assert\Length(array("min" => 3))
            );
        }
    }
    

Геттеры

Ограничения также могут применяться для того, чтобы вернуть значение метода.
Symfony позволяет вам добавлять ограничение к любому публичному методу, имя
которого начинается с «get», «is» или «has». В этой книге, подобные методы
называются общим словом «геттеры».

Преимуществом этой техники является то, что она позволяет вам валидировать ваш
объект динамично. Например, представьте, что вам нужно убедиться, что поле пароля
не совпадает с именем пользователя (из соображений безопасности). Вы можете
сделать это создав метод и указав, что этот метод должен
вернуться как :

  • Annotations

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    // src/AppBundle/Entity/Author.php
    
    // ...
    use Symfony\Component\Validator\Constraints as Assert;
    
    class Author
    {
        /**
         * @Assert\IsTrue(message = "The password cannot match your first name")
         */
        public function isPasswordLegal()
        {
            // ... return true or false
        }
    }
    
  • YAML

    1
    2
    3
    4
    5
    # src/AppBundle/Resources/config/validation.yml
    AppBundle\Entity\Author
        getters
            passwordLegal
                - 'IsTrue' { message 'Thepasswordcannotmatchyourfirstname' }
    
  • XML

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    <?xml version="1.0" encoding="UTF-8" ?>
    xmlns="http://symfony.com/schema/dic/constraint-mapping"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping
            http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
    
        name="AppBundle\Entity\Author">
            property="passwordLegal">
                name="IsTrue">
                    name="message">The password cannot match your first name
    
                
            
        
    
    
  • PHP

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    // src/AppBundle/Entity/Author.php
    
    // ...
    use Symfony\Component\Validator\Mapping\ClassMetadata;
    use Symfony\Component\Validator\Constraints as Assert;
    
    class Author
    {
        public static function loadValidatorMetadata(ClassMetadata $metadata)
        {
            $metadata->addGetterConstraint('passwordLegal', new Assert\IsTrue(array(
                'message' => 'The password cannot match your first name',
            )));
        }
    }
    

Теперь создайте метод и добавьте его в нужную вам логику:

1
2
3
4
public function isPasswordLegal()
{
    return $this->firstName !== $this->password;
}

Note

Самые внимательные из вас заметили, что префикс геттера («get», «is» или «has»)
опущен при отображении. Это позволит вам применить ограничение к свойству с
таким же именем позже ( или наоборот), не изменяя логики валидации.

Managing Errors and 404 Pages¶

When things are not found, you should return a 404 response. To do this, throw a
special type of exception:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

// ...
public function index()
{
    // retrieve the object from database
    $product = ...;
    if (!$product) {
        throw $this->createNotFoundException('The product does not exist');

        // the above is just a shortcut for:
        // throw new NotFoundHttpException('The product does not exist');
    }

    return $this->render(...);
}

The
method is just a shortcut to create a special

object, which ultimately triggers a 404 HTTP response inside Symfony.

If you throw an exception that extends or is an instance of
, Symfony will
use the appropriate HTTP status code. Otherwise, the response will have a 500
HTTP status code:

// this exception ultimately generates a 500 status error
throw new \Exception('Something went wrong!');

In every case, an error page is shown to the end user and a full debug
error page is shown to the developer (i.e. when you’re in «Debug» mode — see
).

Field Options¶

type: , or

This is the property that should be used for displaying the entities as text in
the HTML element:

1
2
3
4
5
6
7
8
use App\Entity\Category;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
// ...

$builder->add('category', EntityType::class, 
    'class' => Category::class,
    'choice_label' => 'displayName',
]);

If left blank, the entity object will be cast to a string and so must have a
method. You can also pass a callback function for more control:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use App\Entity\Category;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
// ...

$builder->add('category', EntityType::class, 
    'class' => Category::class,
    'choice_label' => function ($category) {
        return $category->getDisplayName();
    }
]);

The method is called for each entity in the list and passed to the function. For
more details, see the main documentation.

Note

When passing a string, the option is a property path. So you
can use anything supported by the
PropertyAccessor component

For example, if the translations property is actually an associative
array of objects, each with a property, then you could do this:

1
2
3
4
5
6
7
8
use App\Entity\Genre;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
// ...

$builder->add('genre', EntityType::class, 
    'class' => Genre::class,
    'choice_label' => 'translations.name',
]);

type: required

The class of your entity (e.g. ). This can be
a fully-qualified class name (e.g. )
or the short alias name (as shown prior).

type: | default: the default entity manager

If specified, this entity manager will be used to load the choices
instead of the entity manager.

Learn more¶

When building forms, keep in mind that the first goal of a form is to translate
data from an object () to an HTML form so that the user can modify that
data. The second goal of a form is to take the data submitted by the user and to
re-apply it to the object.

There’s a lot more to learn and a lot of powerful tricks in the Symfony forms:

Reference:

Advanced Features:

  • How to Upload Files
  • How to Implement CSRF Protection
  • How to Access Services or Config from Inside a Form
  • How to Create a Custom Form Field Type
  • How to Use Data Transformers
  • When and How to Use Data Mappers
  • How to Create a Form Type Extension
  • Creating a custom Type Guesser

Form Themes and Customization:

  • Bootstrap 4 Form Theme
  • How to Customize Form Rendering
  • How to Work with Form Themes

Events:

  • Form Events
  • How to Dynamically Modify Forms Using Form Events

Validation:

  • How to Define the Validation Groups to Use
  • How to Dynamically Configure Form Validation Groups
  • How to Choose Validation Groups Based on the Clicked Button
  • How to Disable the Validation of Submitted Data

Misc.:

  • How to Use the submit() Function to Handle Form Submissions
  • How to Embed Forms
  • How to Embed a Collection of Forms
  • How to Reduce Code Duplication with «inherit_data»
  • How to Submit a Form with Multiple Buttons
  • How to Unit Test your Forms
  • How to Configure empty Data for a Form Class
  • How to Use a Form without a Data Class

Rendering Forms¶

Now that the form has been created, the next step is to render it. Instead of
passing the entire form object to the template, use the method
to build another object with the visual representation of the form:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// src/Controller/TaskController.php
namespace App\Controller;

use App\Entity\Task;
use App\Form\Type\TaskType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;

class TaskController extends AbstractController
{
    public function new(Request $request)
    {
        $task = new Task();
        // ...

        $form = $this->createForm(TaskType::class, $task);

        return $this->render('task/new.html.twig', 
            'form' => $form->createView(),
        ]);
    }
}

Then, use some to
render the form contents:

1
2
{# templates/task/new.html.twig #}
{{ form(form) }}

That’s it! The renders all
fields and the start and end tags. By default, the form method is
and the target URL is the same that displayed the form, but
.

Notice how the rendered input field has the value of the
property
from the object (i.e. «Write a blog post»). This is the first
job of a form: to take data from an object and translate it into a format that’s
suitable for being rendered in an HTML form.

Tip

The form system is smart enough to access the value of the protected
property via the and methods on the
class. Unless a property is public, it must have a «getter» and
«setter» method so that Symfony can get and put data onto the property. For
a boolean property, you can use an «isser» or «hasser» method (e.g.
or ) instead of a getter (e.g.
or ).

As short as this rendering is, it’s not very flexible. Usually, you’ll need more
control about how the entire form or some of its fields look. For example, thanks
to the Bootstrap 4 integration with Symfony forms you
can set this option to generate forms compatible with the Bootstrap 4 CSS framework:

  • YAML

    1
    2
    3
    # config/packages/twig.yaml
    twig
        form_themes 'bootstrap_4_layout.html.twig'
    
  • XML

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    <?xml version="1.0" encoding="UTF-8" ?>
    xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:twig="http://symfony.com/schema/dic/twig"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            https://symfony.com/schema/dic/services/services-1.0.xsd
            http://symfony.com/schema/dic/twig
            https://symfony.com/schema/dic/twig/twig-1.0.xsd">
    
        
            bootstrap_4_layout.html.twig
            
        
    
    
  • PHP

    1
    2
    3
    4
    5
    6
    7
    8
    // config/packages/twig.php
    $container->loadFromExtension('twig', 
        'form_themes' => 
            'bootstrap_4_layout.html.twig',
        ],
    
        // ...
    ]);
    

The include
Bootstrap 3 and 4 and Foundation 5. You can also
.

Doctrine Lifecycle Callbacks¶

Lifecycle callbacks are defined as methods inside the entity you want to modify.
For example, suppose you want to set a date column to the current
date, but only when the entity is first persisted (i.e. inserted). To do so,
define a callback for the Doctrine event:

  • Annotations

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // src/Entity/Product.php
    use Doctrine\ORM\Mapping as ORM;
    
    // When using annotations, don't forget to add @ORM\HasLifecycleCallbacks()
    // to the class of the entity where you define the callback
    
    /**
     * @ORM\Entity()
     * @ORM\HasLifecycleCallbacks()
     */
    class Product
    {
        // ...
    
        /**
         * @ORM\PrePersist
         */
        public function setCreatedAtValue()
        {
            $this->createdAt = new \DateTime();
        }
    }
    
  • YAML

    1
    2
    3
    4
    5
    6
    # config/doctrine/Product.orm.yml
    App\Entity\Product
        type entity
        # ...
        lifecycleCallbacks
            prePersist 'setCreatedAtValue'
    
  • XML

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    <?xml version="1.0" encoding="UTF-8" ?>
    xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
            https://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
    
        name="App\Entity\Product">
            
            
                type="prePersist" method="setCreatedAtValue"/>
            
        
    
    

2c) Encoding Passwords¶

Not all applications have «users» that need passwords. If your users have passwords,
you can control how those passwords are encoded in . The
command will pre-configure this for you:

  • YAML

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    # config/packages/security.yaml
    security
        # ...
    
        encoders
            # use your user class name here
            App\Entity\User
                # Use native password encoder
                # This value auto-selects the best possible hashing algorithm
                # (i.e. Sodium when available).
                algorithm auto
    
  • XML

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <?xml version="1.0" encoding="UTF-8"?>
    xmlns="http://symfony.com/schema/dic/security"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:srv="http://symfony.com/schema/dic/services"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            https://symfony.com/schema/dic/services/services-1.0.xsd">
    
        
            
    
            class="App\Entity\User"
                algorithm="auto"
                cost="12"/>
    
            
        
    
    
  • PHP

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    // config/packages/security.php
    use App\Entity\User;
    
    $container->loadFromExtension('security', 
        // ...
    
        'encoders' => 
            User::class => 
                'algorithm' => 'auto',
                'cost' => 12,
            
        ],
    
        // ...
    ]);
    

Now that Symfony knows how you want to encode the passwords, you can use the
service to do this before saving your users to
the database.

For example, by using , you can
create dummy database users:

1
2
3
4
$ php bin/console make:fixtures

The class name of the fixtures to create (e.g. AppFixtures):
> UserFixtures

Use this service to encode the passwords:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// src/DataFixtures/UserFixtures.php

+ use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
// ...

class UserFixtures extends Fixture
{
+     private $passwordEncoder;

+     public function __construct(UserPasswordEncoderInterface $passwordEncoder)
+     {
+         $this->passwordEncoder = $passwordEncoder;
+     }

    public function load(ObjectManager $manager)
    {
        $user = new User();
        // ...

+         $user->setPassword($this->passwordEncoder->encodePassword(
+             $user,
+             'the_new_password'
+         ));

        // ...
    }
}

You can manually encode a password by running:

Processing Forms¶

The is to
use a single action for both rendering the form and handling the form submit.
You can use separate actions, but using one action simplifies everything while
keeping the code concise and maintainable.

Processing a form means to translate user-submitted data back to the properties
of an object. To make this happen, the submitted data from the user must be
written into the form object:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// ...
use Symfony\Component\HttpFoundation\Request;

public function new(Request $request)
{
    // just setup a fresh $task object (remove the example data)
    $task = new Task();

    $form = $this->createForm(TaskType::class, $task);

    $form->handleRequest($request);
    if ($form->isSubmitted() && $form->isValid()) {
        // $form->getData() holds the submitted values
        // but, the original `$task` variable has also been updated
        $task = $form->getData();

        // ... perform some action, such as saving the task to the database
        // for example, if Task is a Doctrine entity, save it!
        // $entityManager = $this->getDoctrine()->getManager();
        // $entityManager->persist($task);
        // $entityManager->flush();

        return $this->redirectToRoute('task_success');
    }

    return $this->render('task/new.html.twig', 
        'form' => $form->createView(),
    ]);
}

This controller follows a common pattern for handling forms and has three
possible paths:

  1. When initially loading the page in a browser, the form hasn’t been submitted
    yet and returns . So, the form is created
    and rendered;
  2. When the user submits the form,
    recognizes this and immediately writes the submitted data back into the
    and properties of the object. Then this object
    is validated (validation is explained in the next section). If it is invalid,
    returns
    and the form is rendered again, but now with validation errors;
  3. When the user submits the form with valid data, the submitted data is again
    written into the form, but this time
    returns . Now you have the opportunity to perform some actions using
    the object (e.g. persisting it to the database) before redirecting
    the user to some other page (e.g. a «thank you» or «success» page);

Note

Redirecting a user after a successful form submission is a best practice
that prevents the user from being able to hit the «Refresh» button of
their browser and re-post the data.

Caution

The method should be called after is
called. Otherwise, when using form events, changes done
in the events won’t be applied to the view (like validation errors).

An Autowiring Example¶

Imagine you’re building an API to publish statuses on a Twitter feed, obfuscated
with ROT13, a fun encoder that shifts all characters 13 letters forward in
the alphabet.

Start by creating a ROT13 transformer class:

1
2
3
4
5
6
7
8
9
namespace App\Util;

class Rot13Transformer
{
    public function transform($value)
    {
        return str_rot13($value);
    }
}

And now a Twitter client using this transformer:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
namespace App\Service;

use App\Util\Rot13Transformer;

class 
{
    private $transformer;

    public function __construct(Rot13Transformer $transformer)
    {
        $this->transformer = $transformer;
    }

    public function tweet($user, $key, $status)
    {
        $transformedStatus = $this->transformer->transform($status);

        // ... connect to Twitter and send the encoded status
    }
}

If you’re using the ,
both classes are automatically registered as services and configured to be autowired.
This means you can use them immediately without any configuration.

However, to understand autowiring better, the following examples explicitly configure
both services:

  • YAML

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    # config/services.yaml
    services
        _defaults
            autowire true
            autoconfigure true
        # ...
    
        
            # redundant thanks to _defaults, but value is overridable on each service
            autowire true
    
        App\Util\Rot13Transformer
            autowire true
    
  • XML

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    <?xml version="1.0" encoding="UTF-8" ?>
    xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
    
        
            autowire="true" autoconfigure="true"/>
            
    
            
            id= autowire="true"/>
    
            id="App\Util\Rot13Transformer" autowire="true"/>
        
    
    
  • PHP

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    // config/services.php
    return function(ContainerConfigurator $configurator) {
        $services = $configurator->services()
            ->defaults()
                ->autowire()
                ->autoconfigure()
        ;
    
        $services->set(::class)
            // redundant thanks to defaults, but value is overridable on each service
            ->autowire();
    
        $services->set(Rot13Transformer::class)
            ->autowire();
    };
    

Now, you can use the service immediately in a controller:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
namespace App\Controller;

use ;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

class DefaultController extends AbstractController
{
    /**
     * @Route("/tweet", methods={"POST"})
     */
    public function tweet( )
    {
        // fetch $user, $key, $status from the POST'ed data

        ->tweet($user, $key, $status);

        // ...
    }
}

Importing Many Services at once with resource¶

You’ve already seen that you can import many services at once by using the
key. For example, the default Symfony configuration contains this:

  • YAML

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # config/services.yaml
    services
        # ... same as before
    
        # makes classes in src/ available to be used as services
        # this creates a service per class whose id is the fully-qualified class name
        App\
            resource '../src/*'
            exclude '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
    
  • XML

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    <?xml version="1.0" encoding="UTF-8" ?>
    xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            https://symfony.com/schema/dic/services/services-1.0.xsd">
    
        
            
    
            namespace="App\" resource="../src/*" exclude="../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}"/>
        
    
    
  • PHP

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    // config/services.php
    namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    
    return function(ContainerConfigurator $configurator) {
        // ...
    
        // makes classes in src/ available to be used as services
        // this creates a service per class whose id is the fully-qualified class name
        $services->load('App\\', '../src/*')
            ->exclude('../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}');
    };
    

Tip

The value of the and options can be any valid
glob pattern.

This can be used to quickly make many classes available as services and apply some
default configuration. The of each service is its fully-qualified class name.
You can override any service that’s imported by using its id (class name) below
(e.g. see ). If you override a service, none of
the options (e.g. ) are inherited from the import (but the overridden
service does still inherit from ).

You can also certain paths. This is optional, but will slightly increase
performance in the environment: excluded paths are not tracked and so modifying
them will not cause the container to be rebuilt.

Note

Wait, does this mean that every class in is registered as
a service? Even model classes? Actually, no. As long as you keep your imported services as , all
classes in that are not explicitly used as services are
automatically removed from the final container. In reality, the import
means that all classes are «available to be used as services» without needing
to be manually configured.

Запрашивание объектов: Repository¶

Вы уже видели, как объект repository позволяет вам выполнять базовые
запросы без каких-либо усилий:

1
2
3
4
// from inside a controller
$repository = $this->getDoctrine()->getRepository(Product::class);

$product = $repository->find($id);

Но что, если вам нужен более сложный запрос? Когда вы сгенерировали свою сущность
с помощью , команда также сгенерировала класс :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// src/Repository/ProductRepository.php
namespace App\Repository;

use App\Entity\Product;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Persistence\ManagerRegistry;

class ProductRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Product::class);
    }
}

Когда вы извлекаете ваше хранилище (т.е. ), оно
на самом деле является экземпляром этого объекта! Это так из-за конфигурации
, которая была создана поверх вашего класса сушности.

Представьте, что вы хотите получить все объекты Product с ценой, больше, чем заданная.
Добавьте для этого в ваше хранилище новый метод:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// src/Repository/ProductRepository.php

// ...
class ProductRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Product::class);
    }

    /**
     * @return Product[]
     */
    public function findAllGreaterThanPrice($price) array
    {
        $entityManager = $this->getEntityManager();

        $query = $entityManager->createQuery(
            'SELECT p
            FROM App\Entity\Product p
            WHERE p.price > :price
            ORDER BY p.price ASC'
        )->setParameter('price', $price);

        // returns an array of Product objects
        return $query->getResult();
    }
}

Строка передаваемая в может показаться похожей на SQL, но это
Doctrine Query Language. Это позволяет вам создавать запросы используя популярный
язык запросов, но ссылаться вместо таблиц на PHP-объекты (например в выражении ).

Теперь, вы можете вызать этот метод в repository:

1
2
3
4
5
6
7
8
// from inside a controller
$minPrice = 1000;

$products = $this->getDoctrine()
    ->getRepository(Product::class)
    ->findAllGreaterThanPrice($minPrice);

// ...

См. , чтобы узнать как внедрить repository в
любой сервис.

Выполнение запросов с конструктором запросов Query Builder

Doctrine также предоставляет Query Builder, объектно-ориентированный способ писать
запросы. Рекомендуется использовать его, когда запросы создаются динамически (то есть,
базируясь на условной логике в PHP):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/Repository/ProductRepository.php

// ...
public function findAllGreaterThanPrice($price, $includeUnavailableProducts = false) array
{
    // automatically knows to select Products
    // the "p" is an alias you'll use in the rest of the query
    $qb = $this->createQueryBuilder('p')
        ->where('p.price > :price')
        ->setParameter('price', $price)
        ->orderBy('p.price', 'ASC');

    if (!$includeUnavailableProducts) {
        $qb->andWhere('p.available = TRUE')
    }

    $query = $qb->getQuery();

    return $query->execute();

    // to get just one result:
    // $product = $query->setMaxResults(1)->getOneOrNullResult();
}

Subversion

Хорошим тоном является использование контроля версий при создании веб приложений.
Использование контроля версий даёт нам:

  • быстро работать
  • вернуться к предущей версии, если что-то поломалось
  • эффективную работу команды разрабочиков
  • доступ ко всем удачным версиям приложения

В данной части мы расскажем как пользоваться
Subversion.
Если вы используете другой инструмент контроля версий, то будет весьма легко адаптироваться
под Subversion.

Мы надеемся что у вас уже есть доступ к серверу Subversion.

tip

Если у ещё нет в распоряжении сервера Subversion, то вы можете воспользоваться
одним из таких на Google Code или просто погуглить
«free subversion repository».

Для начала создадим каталог для проекта :

Затем удалите всё из и — их содержимое нам
в репозитории не нужно.

Теперь убедитесь, что права на cache и log стоят нужные — web server должен в них свободно писать:

Итак, делаем первый импорт:

Т.к. файлы из and нам не нужны, стоит
прописать игнорирование:

Должен запустить текстовый редактор, настроенный на работу с svn.
Subversion должен игнорировать любой контент в данном каталоге:

Сохраняемся и закрываем. Вот и всё.

Повторите процедуру для каталога :

И введите:

И теперь отправим изменения в репозиторий:

tip

Пользователи Windows могут использовать замечательный клиент
TortoiseSVN
для управления своим репозиторием.

note

SVN репозиторий Jobeet будет с каждым днём будет доступней.

И хотя пока ещё не весть репозитарий доступен
(), сегодняшний код был выложет и теггирован.

Вы можете проверить :

A Simple Controller¶

While a controller can be any PHP callable (function, method on an object,
or a ), a controller is usually a method inside a controller
class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// src/Controller/LuckyController.php
namespace App\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class LuckyController
{
    /**
     * @Route("/lucky/number/{max}", name="app_lucky_number")
     */
    public function number($max)
    {
        $number = random_int(, $max);

        return new Response(
            'Lucky number: '.$number.''
        );
    }
}

The controller is the method, which lives inside the
controller class .

This controller is pretty straightforward:

  • line 2: Symfony takes advantage of PHP’s namespace functionality to
    namespace the entire controller class.
  • line 4: Symfony again takes advantage of PHP’s namespace functionality:
    the keyword imports the class, which the controller
    must return.
  • line 7: The class can technically be called anything, but it’s suffixed
    with by convention.
  • line 12: The action method is allowed to have a argument thanks to the
    wildcard in the route.
  • line 16: The controller creates and returns a object.

Learn more¶

  • The Symfony 3.3 DI Container Changes Explained (autowiring, _defaults, etc)
  • How to Create Service Aliases and Mark Services as Private
  • Defining Services Dependencies Automatically (Autowiring)
  • Service Method Calls and Setter Injection
  • How to Work with Compiler Passes
  • How to Configure a Service with a Configurator
  • How to Debug the Service Container & List Services
  • How to work with Service Definition Objects
  • How to Inject Values Based on Complex Expressions
  • Using a Factory to Create Services
  • How to Import Configuration Files/Resources
  • Types of Injection
  • Lazy Services
  • How to Make Service Arguments/References Optional
  • How to Manage Common Dependencies with Parent Services
  • How to Retrieve the Request from the Service Container
  • How to Decorate Services
  • Service Subscribers & Locators
  • How to Define Non Shared Services
  • How to Inject Instances into the Container
  • How to Work with Service Tags
Ссылка на основную публикацию