📜 ⬆️ ⬇️

Creating your own vendor bundle in symfony2

Often there is a need to use the same code in different projects. To avoid repetition of the code, such code is usually placed in the library. In the symfony2 framework, all code should be placed in so-called bundles. Already, there are a huge number of bundles that solve completely different tasks, but still there is often a need to create your own bundle that solves a routine task.

This may be a regular bundle, located in the src folder, and then, if necessary, use it in a new project, copy it to a new project. But in this case there is a problem with updating the code, because when the code is available for change, it will be changed (special perverts even change the code in the vendor folder). For the convenience of using your code in other projects, you can make a bundle as an external, vendor bundle, and manage it through the composer along with the other third-party bundles.

This article shows step by step how to create a bundle from scratch that can be installed via composer.
')
Content:
  1. Create a new bundle
  2. Adding settings to the bundle
  3. Preparation of the bundle for publication
  4. Bundle Publishing

The creation of a bundle to manage static pages of the site will be considered. You can find several ready-made similar bundles, but they are either too simple or too complex (like SonataPageBundle). Article level - advanced beginner. It is understood that the reader already knows how to create bundles in the project, as well as use controllers and templates.

1. Creating a new bundle


1.1. Bundle generation and initial setup


Install symfony
In this article I will use the new symfony 2.6 installation obtained by the command 'composer create-project symfony / framework-standard-edition path /'. But you are free to use a ready-made project, it will not change in this article, only two new bundles will be added.

Creating a vendor bundle is easiest to start with a regular bundle. Therefore, we use the generate: bundle command to create it. Here you need to carefully consider the correct naming, because under this name your bundle will be publicly available. Usually bundles are called by the name of the developer or company and by the name of the bundle itself. Therefore, when creating a bundle, I specified the name space Lexxpavlov / PageBundle - my name and simple simple name. Based on the namespace, the name of the bundle itself is automatically suggested, in my case LexxpavlovPageBundle. This name can be changed, but it suits me, so you can leave it. You can read more about the naming of the bundle here .

When creating a bundle, special attention should be paid to one parameter - the choice of configuration type. The symphony offers four different options - yml, xml, php, or annotation. But the real choice is between yml and annotation, that is, between the choice of configuration in separate YAML format files, or in the format of annotations placed directly in the comments in the code of the controllers and entities. On this account, many copies were broken in this topic , there are arguments in both versions. My choice in this case is annotations, because the project is very small, and the advantages of individual configuration files are leveled (in fact, only one file will have a configuration — a doctrine essence). The type of configuration does not affect the performance of the bundle itself in production - in any case, the entire config is cached.

Next, you should confirm that you are ready to generate a new bundle, and then agree to automatically update the AppKernel.php file and the configuration of app / config / routes.yml routes.

1.2. Creating an entity for doctrine


It's time to create an entity that will contain future pages. Obviously, the required fields are id, title, and content. It will also be useful to have a boolean field published, for the ability to temporarily disable the page display, as well as the createdAt and updatedAt fields with the date the page was created and last modified. For SEO purposes, it is useful to add a field to store the page title in a “urlified” form, usually this field is called slug, as well as the keywords and description fields. Create an Entity folder in the bundle folder, and create a Page.php file in it:
Page Entity class Page.php
<?php namespace Lexxpavlov\PageBundle\Entity; use Doctrine\ORM\Mapping as ORM; use Gedmo\Mapping\Annotation as Gedmo; /** * @ORM\Entity */ class Page { /** * @var integer * @ORM\Column(type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ protected $id; /** * @var string * @Gedmo\Slug(fields={"title"}, updatable=false) * @ORM\Column(type="string", length=100, unique=true) */ protected $slug; /** * @var string * * @ORM\Column(type="string", length=255) */ protected $title; /** * @var string * @ORM\Column(type="text") */ protected $content; /** * @var string * @ORM\Column(type="text", name="keywords", nullable=true) */ protected $keywords; /** * @var string * @ORM\Column(type="text", name="description", nullable=true) */ protected $description; /** * @var boolean * @ORM\Column(type="boolean", options={"default":false}) */ protected $published = false; /** * @var \Datetime * @Gedmo\Timestampable(on="create") * @ORM\Column(type="datetime", name="created_at") */ protected $createdAt; /** * @var \Datetime * @Gedmo\Timestampable(on="update") * @ORM\Column(type="datetime", name="updated_at") */ protected $updatedAt; public function __toString() { return $this->title ?: 'n/a'; } /** * Get id * @return integer */ public function getId() { return $this->id; } /** * Set slug * @param string $slug * @return Page */ public function setSlug($slug) { $this->slug = $slug; return $this; } /** * Get slug * @return string */ public function getSlug() { return $this->slug; } /** * Set title * @param string $title * @return Page */ public function setTitle($title) { $this->title = $title; return $this; } /** * Get title * @return string */ public function getTitle() { return $this->title; } /** * Set content * @param string $content * @return Page */ public function setContent($content) { $this->content = $content; return $this; } /** * Get content * @return string */ public function getContent() { return $this->content; } /** * Set meta keywords * @param string $keywords * @return Page */ public function setKeywords($mkeywords) { $this->keywords = $keywords; return $this; } /** * Get meta keywords * @return string */ public function getKeywords() { return $this->keywords; } /** * Set meta description * @param string $description * @return Page */ public function setDescription($description) { $this->description = $description; return $this; } /** * Set meta description * @return string */ public function getDescription() { return $this->description; } /** * Set published * @param boolean $published * @return Page */ public function setPublished($published) { $this->published = $published; return $this; } /** * Toggle published * @return Page */ public function togglePublished() { $this->published = !$this->published; return $this; } /** * Get published * @return boolean */ public function getPublished() { return $this->published; } /** * Sets created at * @param DateTime $createdAt * @return Page */ public function setCreatedAt(\DateTime $createdAt) { $this->createdAt = $createdAt; return $this; } /** * Returns created at * @return DateTime */ public function getCreatedAt() { return $this->createdAt; } /** * Sets updated at * @param DateTime $updatedAt * @return Page */ public function setUpdatedAt(\DateTime $updatedAt) { $this->updatedAt = $updatedAt; return $this; } /** * Returns updated at * @return DateTime */ public function getUpdatedAt() { return $this->updatedAt; } } 


In addition to the well-known @ORM annotations supported by the doctrine, the @Gedmo annotations provided by the StofDoctrineExtensionsBundle bundle are used here. To make these annotations work, you need to add this bundle to the system. To do this, use the instructions in its documentation, installing the package by changing the composer.json of our project. To use the annotations used, this configuration is sufficient:
 stof_doctrine_extensions: orm: default: sluggable: true timestampable: true 

Creating a table in the database is done with the command 'app / console doctrine: schema: update --force' (if the database itself has not been created yet, you must first run the command 'app / console doctrine: database: create'). This command will create a table with the name Page or page, depending on the database settings. As far as I have seen in other bundles, most often third-party bundles create tables without capital letters, because otherwise in some cases problems may arise. But in this case it does not matter, later this slippery moment will be eliminated.

1.3. Creating and using forms to create new pages


The next step is to write the form type to create the pages. To do this, you need to create the Form / Type folder in the bundle and create a PageType.php file in it. The content of this file is quite trivial - we create a descendant class AbstractType and specify in the buildForm () method all the fields required for filling:
 <?php namespace Lexxpavlov\PageBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; class PageType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('title', 'text') ->add('slug', 'text', array('required' => false)) ->add('content', 'textarea') ->add('published', 'checkbox', array('required' => false)) ->add('keywords', 'text', array('required' => false)) ->add('description', 'text', array('required' => false)) ->add('save', 'submit') ; } public function getName() { return 'lexxpavlov_page'; } } 

Do not forget to specify the form name, which has the name of the bundle in the prefix - `lexxpavlov_page`. It is by this name that you need to create a service from the form type, and specifying the vendor prefix will help avoid conflicts in the project. To do this, create the configuration file Resources / config / services.yml and add the following code to it:
 services: lexxpavlov_page.form.type.page: class: Lexxpavlov\PageBundle\Form\Type\PageType tags: - { name: form.type, alias: lexxpavlov_page } 

The services.xml file is already in the Resources / config folder, offering to create a description of services in verbose XML. Since we wrote a description of the service in YAML, you need to delete the services.xml file and configure the connection of the services.yml file. To do this, open the bundle dependency injector setting - the DependencyInjector / LexxpavlovPageExtension.php file and fix the reading of the XML file in YAML:
 // ... $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services.yml'); 

Add in the controller created by the generator DefaultController actions for viewing the list of pages, for viewing one page and for adding a new page, let's also add templates for them:
 <?php namespace AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; use Symfony\Component\HttpFoundation\Request; use Lexxpavlov\PageBundle\Entity\Page; /** * @Route("/page") */ class DefaultController extends Controller { /** * @Route("/", name="page") * @Template() */ public function indexAction() { $pages = $this->getDoctrine()->getManager() ->getRepository('LexxpavlovPageBundle:Page') ->findAll(); return array( 'pages' => $pages, ); } /** * @Route("/show/{slug}", name="page_show") * @Template() */ public function showAction(Page $page) { } /** * @Route("/new", name="page_new") * @Template() */ public function newAction(Request $request) { $page = new Page(); $form = $this->createForm('lexxpavlov_page', $page); if ($request->isMethod('POST')) { $form->handleRequest($request); if ($form->isValid()) { $em = $this->getDoctrine()->getManager(); $em->persist($page); $em->flush(); return $this->redirect($this->generateUrl('page')); } } return array( 'form' => $form->createView(), ); } } 

 {# src/Lexxpavlov/PageBundle/Resources/views/Default/index.html.twig #} <ul> {% for page in pages %} <li><a href="{{ path('page_show', {slug: page.slug}) }}">{{ page.title }}</a></li> {% endfor %} </ul> <a href="{{ path('page_new') }}">  </a> {# src/Lexxpavlov/PageBundle/Resources/views/Default/show.html.twig #} <article> <h1>{{ page.title }}</h1> <div> : <time datetime="{{ page.createdAt|date('Ym-d') }}" pubdate>{{ page.createdAt|date('dmY') }}</time></div> {{ page.content|raw }} </article> {# src/Lexxpavlov/PageBundle/Resources/views/Default/new.html.twig #} <h1> </h1> {{ form(form) }} 

Disclaimer on controller code
I note that in the future the controller will be removed from the bundle, so I did not try to write a beautiful and effective code. The current controller code is not a reference for such tasks. On good, should transfer the code of work with a database to services. But the article is not about controllers, so I did not inflate an already long article. Thanks to Fesor for the comments.


1.4. Creating a class for the Sonata


Also create a class for admin SonataAdminBundle - a popular administrative project panel on Symfony2. For this, firstly, you need to add the SonataAdminBundle admin panel itself to the project (there is an old article about installing SonataAdminBundle, I plan to write a new one on this topic). Next you need to create a file Admin / Page.php:
The class for managing the entity in SonataAdminBundle
 <?php namespace Lexxpavlov\PageBundle\Admin; use Sonata\AdminBundle\Admin\Admin; use Sonata\AdminBundle\Form\FormMapper; use Sonata\AdminBundle\Datagrid\DatagridMapper; use Sonata\AdminBundle\Datagrid\ListMapper; use Sonata\AdminBundle\Show\ShowMapper; class PageAdmin extends Admin { public function configureListFields(ListMapper $listMapper) { $listMapper ->addIdentifier('title') ->add('slug') ->add('published', null, array('editable' => true)) ->add('createdAt', 'datetime') ->add('updatedAt', 'datetime') ; } public function configureFormFields(FormMapper $formMapper) { $formMapper ->with('General') ->add('slug', null, array('required' => false)) ->add('title') ->add('content') ->add('published', null, array('required' => false)) ->end() ->with('SEO') ->add('keywords', null, array('required' => false)) ->add('description', null, array('required' => false)) ->end() ; $formMapper->setHelps(array( 'slug' => 'Leave blank for automatic filling from title field', )); } public function configureDatagridFilters(DatagridMapper $datagridMapper) { $datagridMapper ->add('slug') ->add('title') ->add('published') ; } public function configureShowFields(ShowMapper $showMapper) { $showMapper ->add('slug') ->add('title') ->add('content') ->add('published') ->add('publishedAt', 'datetime') ->add('createdAt', 'datetime') ->add('updatedAt', 'datetime') ->add('keywords') ->add('description') ; } } 


Now, in order for the Sonata to see it, you need to announce a service for it. Services for the Sonata, I usually describe in a separate configuration file - Resources / config / admin.yml:
 services: sonata.admin.lexxpavlov_page: class: Lexxpavlov\PageBundle\Admin\Page tags: - { name: sonata.admin, manager_type: orm, group: "Content", label: "Pages", label_catalogue: "messages" } arguments: - ~ - Lexxpavlov\PageBundle\Entity\Page - ~ calls: - [ setTranslationDomain, [messages]] 

And now you need to add the download of this file in the DependencyInjector / LexxpavlovPageExtension.php configurator:
 $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services.yml'); $loader->load('admin.yml'); 


1.5. Checking the work of the bundle


It's time to check the work done - go to the page of the site / page / new, add a page with the title “Test” on it, see this page in the list on / page / and view it on / page / show / tiest, at the same time checking the work @ Gedmo / Slug. On the first page you need to add a new page, on the second - to see the newly created page in the list, and on the third - open and look at it.
Features of the transliteration of Russian characters
Handler @ Gedmo / Slug can automatically convert Russian words into transliteration. But doing it badly adds extra vowels, so the word “Test” becomes the word “tiest”. Therefore, you need to teach him the proper transformation. This is done very simply - a special class Listener \ SluggableListener is created, which can perform the conversion, and is set as the event handler for the Sluggable event:
 <?php namespace Lexxpavlov\PageBundle\Listener; use Gedmo\Sluggable\SluggableListener as BaseSluggableListener; use Gedmo\Sluggable\Util\Urlizer; class SluggableListener extends BaseSluggableListener { public function __construct(){ $this->setTransliterator(array($this, 'transliterate')); } public function transliterate($text, $separator = '-') { $convertTable = array( '' => 'a', '' => 'b', '' => 'v', '' => 'g', '' => 'd', '' => 'e', '' => 'e', '' => 'zh', '' => 'z', '' => 'i', '' => 'j', '' => 'k', '' => 'l', '' => 'm', '' => 'n', '' => 'o', '' => 'p', '' => 'r', '' => 's', '' => 't', '' => 'u', '' => 'f', '' => 'h', '' => 'ts', '' => 'ch', '' => 'sh', '' => 'sch', '' => '', '' => 'y', '' => '', '' => 'e', '' => 'yu', '' => 'ya' ); $text = strtr(trim(mb_strtolower($text, 'UTF-8')), $convertTable); return Urlizer::urlize($text, $separator); } } 

 stof_doctrine_extensions: orm: default: sluggable: true timestampable: true class: sluggable: Lexxpavlov\PageBundle\Listener\SluggableListener 


This completes the preliminary preparation, the bundle is ready for use, including being ready for copying to other projects. If it were a regular bundle for work, we could stop at that, but now we need to give it the flexibility to set up the vendor bundles and prepare it for publication.

2. Adding settings to the bundle


2.1. The possibility of expanding the essence of the page


An important distinction of vendor bundles is their ability to adapt to the needs of using it. For example, it is required, in addition to the fields indicated in the page binder, to add a new field with the name of the author of the page and a field that stores the author of the last edits on the page. How to do it? Write a new entity class? Long and tedious. Inherit it from the already finished? Already better, besides not in vain in the class of entity fields are marked as protected, and not private.
Doctrine and private fields in essence
In fact, the protected mark is needed only to allow access to the fields of the superclass from the methods of the inheriting class. If the fields in the superclass are marked as private, then the Doctrine can still be configured to use such fields in the inheriting class, for this the MappedSuperclass annotation is used . If the superclass is marked with this annotation, then even private fields will fall into the heir classes. There is one feature. Often, a freshly written entity is left without getters and setters, in order to ask the doctrine to create them automatically with the console command doctrine: generate: entities. But if these getters / setters are already in the superclass, the doctrine will still try to create getters for the protected-fields from the superclass in the inheriting class.

A good solution is implemented in the popular FOSUserBundle bundle - there are no entities in the bundle itself, that is, classes marked with the @ORM \ Entity annotation. Let's do the same trick in our bundle. You need to remove the @ORM \ Entity annotation before the Lexxpavlov / PageBundle / Entity / Page.php class. To create an entity, you need to create a class that inherits this class, mark it as @ORM \ Entity, and you can add the required additional custom fields to it. This class will be a good place for the user to choose their own table naming style — either allow the doctrine to automatically select the table name for the new entity, or specify its name in the @ORM \ Table annotation.

To create a new entity, it is better to create a new bundle. Let's use the generator and place the bundle in the AppBundle namespace (if you installed a new Symfony2 project, then you should already have a bundle with that name). Transfer the DefaultController controller from our bundle (it doesn't need a controller anymore) to the newly created one, transfer the Resources (views) templates, remove the extra link to the bundle in the app / config / routing.yml file, and don't forget to change the new class in the DefaultController controller (in the use construct and in the getRepository () method call). Create a new entity for the pages:
 <?php namespace AppBundle\Entity; use Doctrine\ORM\Mapping as ORM; use Lexxpavlov\PageBundle\Entity\Page as BasePage; /** * @ORM\Entity */ class Page extends BasePage { } 

Since the new class has the same name as the previous one, there is no need to recreate the table in the database, and at this point already created pages should work as before.

But we had the task to expand the essence with new fields - fields for storing the author and the last editor. For this, the Blameable behavior from the StofDoctrineExtensionsBundle bundle already used is well suited. Add them to the newly created class:
Updated class AppBundle \ Entity \ Page
 <?php namespace AppBundle\Entity; use Doctrine\ORM\Mapping as ORM; use Gedmo\Mapping\Annotation as Gedmo; use Lexxpavlov\PageBundle\Entity\Page as BasePage; use AppBundle\Entity\User; /** * @ORM\Entity */ class Page extends BasePage { /** * @var User * @ORM\ManyToOne(targetEntity="User") * @Gedmo\Blameable(on="create") */ protected $createdBy; /** * @var User * @ORM\ManyToOne(targetEntity="User") * @Gedmo\Blameable(on="update") */ protected $updatedBy; /** * Set user, that updated entity * @param User $updatedBy * @return Page */ public function setUpdatedBy($updatedBy) { $this->updatedBy = $updatedBy; return $this; } /** * Get user, that updated entity * @return User */ public function getUpdatedBy() { return $this->updatedBy; } /** * Set user, that created entity * @param User $createdBy * @return Page */ public function setCreatedBy($createdBy) { $this->createdby = $createdBy; return $this; } /** * Get user, that created entity * @return User */ public function getCreatedBy() { return $this->createdBy; } } 


We also need a new class User - to store users. It is best to take it from the FOSUserBundle bundle. Install this bundle and extend the class offered to users:
 <?php namespace AppBundle\Entity; use FOS\UserBundle\Model\User as BaseUser; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity * @ORM\Table(name="users") */ class User extends BaseUser { /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue(strategy="AUTO") */ protected $id; /** * Get id * @return integer */ public function getId() { return $this->id; } } 

Now you need to include the Blameable behavior in the StofDoctrineExtensionsBundle config:
 stof_doctrine_extensions: orm: default: sluggable: true timestampable: true blameable: true 

and update the tables in the database with the console command 'app / console doctrine: schema: update --force'.
Setting up the FOSUserBundle bundle
After installing the FOSUserBundle bundle and its settings (in accordance with its documentation ), you need to add our secure pages to the firewall. We have one page, access to which needs to be limited - this is the page for adding a new page / page / new. You need to add it to the access_control list in the app / config / security.yml file:
 security: # ... access_control: - { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/page/new, role: ROLE_ADMIN } 

Now you need to add a user to the database. The easiest way to do this is to use the console command 'app / console fos: user: create', add administrator privileges (ROLE_ADMIN) with the command 'app / console fos: user: promote', and then activate it with the command 'app / console fos: user: activate '.


2.2. Configuring the bundle configuration


After the changes were made and the creation of the entity was transferred to a separate bundle, the class for the Sonata stopped working, because the class of its service was clearly prescribed the class Lexxpavlov \ PageBundle \ Entity \ Page. But now it is impossible to say exactly in which place and under what name the entity class will be created. To do this, it is best to use the standard way of creating settings in Symphony - specify the description of services in the bundle settings, and place the configuration relating to a specific project in app / config / config.yml.

First of all, you need to create a description of the configuration of our bundle. For this is the standard component Config . The entire description of the config is a sequential invocation of predefined methods that describe the available parameters of the config. Repeating the documentation here will be unnecessary, just give a link to its translation into Russian .
 <?php namespace Lexxpavlov\PageBundle\DependencyInjection; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; class Configuration implements ConfigurationInterface { public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder(); $rootNode = $treeBuilder->root('lexxpavlov_page'); $rootNode ->children() ->scalarNode('entity_class') ->cannotBeEmpty() ->end() ->end() ; return $treeBuilder; } } 

Now you need to add the setting to the config:
 lexxpavlov_page: entity_class: AppBundle\Entity\Page 

and configure the use of this parameter in those places where it is needed - in the Sonata admin service and the form type. To be able to use the value of the class name, you need to save it in the DI container parameter, you need to do this in the DependencyInjection / LexxpavlovPageExtension.php configurator, adding the following line to the end of the load () method:
 $container->setParameter('lexxpavlov_page.entity_class', $config['entity_class']); 

Now the saved parameter can be used in service announcements. The admin service requires you to specify the class name with the second constructor parameter:
 services: sonata.admin.lexxpavlov_page: class: Lexxpavlov\PageBundle\Admin\Page tags: - { name: sonata.admin, manager_type: orm, group: "Content", label: "Pages", label_catalogue: "messages" } arguments: - ~ - %lexxpavlov_page.entity_class% - ~ calls: - [ setTranslationDomain, [messages]] 

In the form service, you need not only to pass the parameter, but also to create a constructor in the form class:
 services: lexxpavlov_page.form.type.page: class: Lexxpavlov\PageBundle\Form\Type\PageType arguments: [ %lexxpavlov_page.entity_class% ] tags: - { name: form.type, alias: lexxpavlov_page } 

Adding a constructor and setting custom form options
 <?php namespace Lexxpavlov\PageBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class PageType extends AbstractType { private $dataClass; public function __construct($dataClass) { $this->dataClass = $dataClass; } public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('title', 'text') ->add('slug', 'text', array('required' => false)) ->add('content', 'textarea') ->add('published', 'checkbox') ->add('keywords', 'text') ->add('description', 'text') ->add('save', 'submit') ; } public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'data_class' => $this->dataClass, )); } public function getName() { return 'lexxpavlov_page'; } } 



2.3. Additional bundle settings


The next step will be to disable registration of the service for the sonata, for those who do not need this service, or for those who want to change this service (for example, add their own entity fields to the admin panel). To do this, add a new parameter to the configuration of the bundle. At the same time, we will add another setting - use the CKEditor wysiwyg editor in the add forms page instead of the usual textarea field (to use this type of form field, IvoryCKEditorBundle bundle is required, later we will mark it recommended for installation).

Add two new elements to the bundle configuration (DependencyInjection / Configuration.php):
 $rootNode ->children() ->scalarNode('entity_class') ->cannotBeEmpty() ->end() ->scalarNode('admin_class') ->defaultValue('Lexxpavlov\PageBundle\Admin\PageAdmin') ->end() ->scalarNode('content_type') ->defaultValue('ckeditor') ->end() ->end() ; 

These parameters are marked as optional, and if they are not specified in the config, they will accept the specified default values. Now save the settings values ​​to the container parameters (in the file DependencyInjection / LexxpavlovPageExtension.php):
 public function load(array $configs, ContainerBuilder $container) { $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services.yml'); $container->setParameter('lexxpavlov_page.entity_class', $config['entity_class']); $container->setParameter('lexxpavlov_page.content_type', $config['content_type']); if ($config['admin_class'] && $config['admin_class'] != 'false') { $loader->load('admin.yml'); $container->setParameter('lexxpavlov_page.admin_class', $config['admin_class']); } } 

Here is the conditional addition of the file with the ad service for the admin. And if you have written your own expanding class for admin panel (which can be inherited from an already ready class), then you can specify its name in the parameter lexxpavlov_page.admin_class.

Now add the use of new parameters to the admin class. Modify the service declaration and expand the class itself:
 services: sonata.admin.lexxpavlov_page: class: %lexxpavlov_page.admin_class% tags: - { name: sonata.admin, manager_type: orm, group: "Content", label: "Pages", label_catalogue: "messages" } arguments: - ~ - %lexxpavlov_page.entity_class% - ~ calls: - [ setTranslationDomain, [messages]] - [ setContentType, [ %lexxpavlov_page.content_type% ] ] 

The final version of the class PageAdmin
 <?php namespace Lexxpavlov\PageBundle\Admin; use Sonata\AdminBundle\Admin\Admin; use Sonata\AdminBundle\Form\FormMapper; use Sonata\AdminBundle\Datagrid\DatagridMapper; use Sonata\AdminBundle\Datagrid\ListMapper; use Sonata\AdminBundle\Show\ShowMapper; class PageAdmin extends Admin { protected $contentType = 'ckeditor'; // or null for simple textarea field public function setContentType($contentType) { $this->contentType = $contentType; } public function configureListFields(ListMapper $listMapper) { $listMapper ->addIdentifier('title') ->add('slug') ->add('published', null, array('editable' => true)) ->add('createdAt', 'datetime') ->add('updatedAt', 'datetime') ; } public function configureFormFields(FormMapper $formMapper) { $formMapper ->with('General') ->add('slug', null, array('required' => false)) ->add('title') ->add('content', $this->contentType) ->add('published', null, array('required' => false)) ->end() ->with('SEO') ->add('keywords', null, array('required' => false)) ->add('description', null, array('required' => false)) ->end() ; $formMapper->setHelps(array( 'slug' => 'Leave blank for automatic filling from title field', )); } public function configureDatagridFilters(DatagridMapper $datagridMapper) { $datagridMapper ->add('slug') ->add('title') ->add('published') ; } public function configureShowFields(ShowMapper $showMapper) { $showMapper ->add('slug') ->add('title') ->add('content') ->add('published') ->add('publishedAt', 'datetime') ->add('createdAt', 'datetime') ->add('updatedAt', 'datetime') ->add('keywords') ->add('description') ; } } 


For a change, I didn’t add a field type parameter to the form class, enabling the use of CKEditor by default and allowing it to be disabled through the form settings when creating it.
The final version of the PageType class
 <?php namespace Lexxpavlov\PageBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class PageType extends AbstractType { private $dataClass; public function __construct($dataClass) { $this->dataClass = $dataClass; } public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('slug', 'text') ->add('title', 'text') ->add('content', $options['contentType']) ->add('published', 'checkbox') ->add('keywords', 'text') ->add('description', 'text') ->add('save', 'submit') ; } public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'data_class' => $this->dataClass, 'contentType' => 'ckeditor', )); } public function getName() { return 'lexxpavlov_page'; } } 



2.4. Removing extra files from a bundle


The bundle is almost finished. It remains to throw out the extra files that were created by the band-generator and which are not used. This is the Controller folder (if you have not deleted it before), the contents of the Resources folder (except Resources / config), as well as the test folder (there is nothing to test in the bundle, no controllers, and everything else is trivial).

3. Preparing the bundle for publication


3.1. Creating the composer.json file


When the bundle is ready for publication, it's time to prepare it for publication. The composer bundles are on packagist.org, and there they get from some public version control system. We will use Github. But first of all, you need to create a file composer.json - a file in JSON format, containing meta information about the package, which in the future will be used by both the composer itself and packagist. The composer.json file is located at the root of the bundle and looks like this:
 { "name" : "lexxpavlov/pagebundle", "description" : "Symfony2 Page bundle with meta data, predefined form type and Sonata Admin service", "version" : "1.0.0", "type" : "symfony-bundle", "homepage": "https://github.com/lexxpavlov/PageBundle", "license" : "MIT", "keywords" : ["page", "page bundle"], "authors" : [{ "name" : "Alexey Pavlov", "email" : "lexx.pavlov@gmail.com" }], "require" : { "php": ">=5.3.2", "symfony/symfony": ">=2.1", "stof/doctrine-extensions-bundle": ">=1.1" }, "suggest": { "egeloen/ckeditor-bundle": "Allow use ckeditor field" }, "autoload" : { "psr-4" : { "Lexxpavlov\\PageBundle\\" : "" } }, "extra" : { "branch-alias" : { "dev-master" : "1.0.x-dev" } } } 

, .

name — . . vendor.
description — . , , Packagist- , .
version — . , , .
type — . Symfony, «symfony-bundle».
homepage — . , .
license — , . MIT — . ( )
keywords — , , .
authors — .
require — , . php, , , . Composer , , , .
suggest — (). , , . 'ckeditor', IvoryCKEditorBundle, suggests, , , . composer-.
autoload — . PSR-4, . PSR-0 , , — vendor/lexxpavlov/pagebundle.
extra- additional package parameters. In this case, the branch-alias parameter is used, which in Packagist creates a new version of the package called dev-master, which usually points to the master branch of the code.
minimum-stability - (not used in the example above) Indicates which dependency branches can be used. If not specified, only the stable versions will be installed. Possible values: dev, alpha, beta, RC and stable. You can read more here .

You also need to create the readme.md file with the bundle documentation. This text file format Markdown allows you to quickly and easily write documentation with text markup and code snippets. Description of the Markdown format can be read here , or see the description markup of our bundle.

3.2.


Git, . - , :
 $ cd /path/to/project/src/Lexxpavlov/PageBundle $ git init $ git add . -A $ git commit -m "Init commit" 

, , , , . , symlink.

3.3. Github


Go to Github to create a new repository page and give the name of the new repository (PageBundle). You can set a brief description of the package, which will be displayed at the top of the main page of the repository immediately below. Important!Make sure that the “Initialize this repository with a README” checkbox is not checked, and the creation of the .gitignore and license file is disabled (the word NONE is displayed in the corresponding drop-down lists). Next, go to the newly created (so far empty) repository and copy the path to it into the buffer (the Copy to clipboard button in the HTTPS clone URL section at the bottom of the right menu). We execute lines in the console:
 $ git remote add origin remote https://github.com/yourusername/YourBundle.git $ git push origin master 

Done! Go to the package page and admire your files!

4. Publishing a bundle


4.1. Using a bundle without registering with Packagist


composer, , . composer.json , , :
 [...] "require" : { [...] "lexxpavlov/pagebundle" : "dev-master" }, "repositories" : [{ "type" : "vcs", "url" : "https://github.com/lexxpavlov/PageBundle.git" }], [...] 

, , , Packagist. — master- . Packagist.

4.2. Packagist


Packagist , . , , , . . , 1.0.0 v1.0.0. ( .) , 1.0.0. , , .
, , composer.json:
 $ git tag 1.0.0 $ git push origin --tags 

. Packagist.org ( Github) Submit package. Check. , Submit. !

The package will be placed in the Packagist.org package archive. You can see the package page with the fields that were extracted from the composer.json file and the package management buttons. You will also see a warning that the package is not auto-updating. An auto-update package is a githab configuration that automatically informs Packagist about updating the repository, and it will re-browse the repository in search of new code versions or update information in the composer.json file. To install autoupdate, go to the profile page on Packagist and follow the instructions there.

Now to use the bundle, just add the name of the package to the composer.json file of your project:
 [...] "require" : { [...] "lexxpavlov/pagebundle" : "1.0.0" }, [...] 

or execute the command to add a package to the console (in the project folder):
 $ php composer.phar require lexxpavlov/pagebundle 


Conclusion


We created a package for the composer package manager, which contains the bundle we wrote for the symfony2 framework. If we use it in several projects, when changing a package, we can get a new version of the code for all projects with a simple “composer update” command. Also, other programmers can use your package for their projects, and repay you with bug reports and pull requests.

Bundle repository (a slightly modified version of the bundle was proposed in the article in order not to increase the article even more)
Packagist bundle page. Materials

used and useful links:
  1. StackOverflow question about creating your own bundle
  2. Creating pages in Symfony2 - a detailed description of the process of creating bundles, controllers and templates
  3. Translation of Config component documentation
  4. About Packagist — Packagist
  5. Choose a license — Github.com

Source: https://habr.com/ru/post/248055/


All Articles