Completed
Pull Request — develop (#147)
by Wachter
18:24 queued 12:29
created

ArticleController::cdeleteAction()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 13
ccs 8
cts 8
cp 1
rs 9.4285
cc 2
eloc 8
nc 2
nop 1
crap 2
1
<?php
2
3
/*
4
 * This file is part of Sulu.
5
 *
6
 * (c) MASSIVE ART WebServices GmbH
7
 *
8
 * This source file is subject to the MIT license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace Sulu\Bundle\ArticleBundle\Controller;
13
14
use FOS\RestBundle\Controller\Annotations\Post;
15
use FOS\RestBundle\Routing\ClassResourceInterface;
16
use JMS\Serializer\SerializationContext;
17
use ONGR\ElasticsearchBundle\Service\Manager;
18
use ONGR\ElasticsearchDSL\Query\BoolQuery;
19
use ONGR\ElasticsearchDSL\Query\IdsQuery;
20
use ONGR\ElasticsearchDSL\Query\MatchAllQuery;
21
use ONGR\ElasticsearchDSL\Query\MatchQuery;
22
use ONGR\ElasticsearchDSL\Query\MultiMatchQuery;
23
use ONGR\ElasticsearchDSL\Query\TermQuery;
24
use ONGR\ElasticsearchDSL\Sort\FieldSort;
25
use Sulu\Bundle\ArticleBundle\Admin\ArticleAdmin;
26
use Sulu\Bundle\ArticleBundle\Document\Form\ArticleDocumentType;
27
use Sulu\Bundle\ArticleBundle\Metadata\ArticleViewDocumentIdTrait;
28
use Sulu\Component\Content\Form\Exception\InvalidFormException;
29
use Sulu\Component\Content\Mapper\ContentMapperInterface;
30
use Sulu\Component\DocumentManager\DocumentManagerInterface;
31
use Sulu\Component\Rest\Exception\MissingParameterException;
32
use Sulu\Component\Rest\Exception\RestException;
33
use Sulu\Component\Rest\ListBuilder\FieldDescriptor;
34
use Sulu\Component\Rest\ListBuilder\ListRepresentation;
35
use Sulu\Component\Rest\RequestParametersTrait;
36
use Sulu\Component\Rest\RestController;
37
use Sulu\Component\Security\SecuredControllerInterface;
38
use Symfony\Component\HttpFoundation\Request;
39
use Symfony\Component\HttpFoundation\Response;
40
41
/**
42
 * Provides API for articles.
43
 */
44
class ArticleController extends RestController implements ClassResourceInterface, SecuredControllerInterface
45
{
46
    const DOCUMENT_TYPE = 'article';
47
48
    use RequestParametersTrait;
49
    use ArticleViewDocumentIdTrait;
50
51
    /**
52
     * Create field-descriptor array.
53
     *
54
     * @return FieldDescriptor[]
55
     */
56
    private function getFieldDescriptors()
57
    {
58
        return [
59
            'uuid' => new FieldDescriptor('uuid', 'public.id', true),
60
            'typeTranslation' => new FieldDescriptor(
61
                'typeTranslation',
62
                'sulu_article.list.type',
63
                !$this->getParameter('sulu_article.display_tab_all'),
64
                false
65
            ),
66
            'title' => new FieldDescriptor('title', 'public.title', false, true),
67
            'creatorFullName' => new FieldDescriptor('creatorFullName', 'sulu_article.list.creator', true, false),
68
            'changerFullName' => new FieldDescriptor('changerFullName', 'sulu_article.list.changer', false, false),
69
            'authorFullName' => new FieldDescriptor('authorFullName', 'sulu_article.author', false, false),
70
            'created' => new FieldDescriptor('created', 'public.created', true, false, 'datetime'),
71
            'changed' => new FieldDescriptor('changed', 'public.changed', false, false, 'datetime'),
72
            'authored' => new FieldDescriptor('authored', 'sulu_article.authored', false, false, 'date'),
73
        ];
74
    }
75
76
    /**
77
     * Returns fields.
78
     *
79
     * @return Response
80
     */
81
    public function cgetFieldsAction()
82
    {
83
        $fieldDescriptors = $this->getFieldDescriptors();
84
85
        return $this->handleView($this->view(array_values($fieldDescriptors)));
0 ignored issues
show
Documentation introduced by
$this->view(array_values($fieldDescriptors)) is of type this<Sulu\Bundle\Article...ller\ArticleController>, but the function expects a object<FOS\RestBundle\View\View>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
86
    }
87
88
    /**
89
     * Returns list of articles.
90
     *
91
     * @param Request $request
92
     *
93
     * @return Response
94
     */
95 12
    public function cgetAction(Request $request)
96
    {
97 12
        $locale = $this->getRequestParameter($request, 'locale', true);
98
99 12
        $restHelper = $this->get('sulu_core.list_rest_helper');
100
101
        /** @var Manager $manager */
102 12
        $manager = $this->get('es.manager.default');
103 12
        $repository = $manager->getRepository($this->get('sulu_article.view_document.factory')->getClass('article'));
104 12
        $search = $repository->createSearch();
105
106 12
        $limit = (int) $restHelper->getLimit();
107 12
        $page = (int) $restHelper->getPage();
108
109 12
        if (null !== $locale) {
110 12
            $search->addQuery(new TermQuery('locale', $locale));
0 ignored issues
show
Deprecated Code introduced by
The class ONGR\ElasticsearchDSL\Query\TermQuery has been deprecated with message: Use the extended class instead. This class is left only for BC compatibility.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
111
        }
112
113 12
        if (count($ids = array_filter(explode(',', $request->get('ids', ''))))) {
114 1
            $search->addQuery(new IdsQuery($this->getViewDocumentIds($ids, $locale)));
0 ignored issues
show
Deprecated Code introduced by
The class ONGR\ElasticsearchDSL\Query\IdsQuery has been deprecated with message: Use the extended class instead. This class is left only for BC compatibility.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
115 1
            $limit = count($ids);
116
        }
117
118 12
        if (!empty($searchPattern = $restHelper->getSearchPattern())
119 12
            && 0 < count($searchFields = $restHelper->getSearchFields())
120
        ) {
121 2
            $search->addQuery(new MultiMatchQuery($searchFields, $searchPattern));
0 ignored issues
show
Deprecated Code introduced by
The class ONGR\ElasticsearchDSL\Query\MultiMatchQuery has been deprecated with message: Use the extended class instead. This class is left only for BC compatibility.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
122
        }
123
124 12
        if (null !== ($type = $request->get('type'))) {
125 10
            $search->addQuery(new TermQuery('type', $type));
0 ignored issues
show
Deprecated Code introduced by
The class ONGR\ElasticsearchDSL\Query\TermQuery has been deprecated with message: Use the extended class instead. This class is left only for BC compatibility.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
126
        }
127
128 12
        if (null !== ($contactId = $request->get('contactId'))) {
129 1
            $boolQuery = new BoolQuery();
0 ignored issues
show
Deprecated Code introduced by
The class ONGR\ElasticsearchDSL\Query\BoolQuery has been deprecated with message: Use the extended class instead. This class is left only for BC compatibility.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
130 1
            $boolQuery->add(new MatchQuery('changer_contact_id', $contactId), BoolQuery::SHOULD);
0 ignored issues
show
Deprecated Code introduced by
The class ONGR\ElasticsearchDSL\Query\MatchQuery has been deprecated with message: Use the extended class instead. This class is left only for BC compatibility.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
131 1
            $boolQuery->add(new MatchQuery('creator_contact_id', $contactId), BoolQuery::SHOULD);
0 ignored issues
show
Deprecated Code introduced by
The class ONGR\ElasticsearchDSL\Query\MatchQuery has been deprecated with message: Use the extended class instead. This class is left only for BC compatibility.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
132 1
            $boolQuery->add(new MatchQuery('author_id', $contactId), BoolQuery::SHOULD);
0 ignored issues
show
Deprecated Code introduced by
The class ONGR\ElasticsearchDSL\Query\MatchQuery has been deprecated with message: Use the extended class instead. This class is left only for BC compatibility.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
133 1
            $search->addQuery($boolQuery);
134
        }
135
136 12
        if (null !== ($categoryId = $request->get('categoryId'))) {
137 1
            $search->addQuery(new TermQuery('excerpt.categories.id', $categoryId), BoolQuery::FILTER);
0 ignored issues
show
Deprecated Code introduced by
The class ONGR\ElasticsearchDSL\Query\TermQuery has been deprecated with message: Use the extended class instead. This class is left only for BC compatibility.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
138
        }
139
140 12
        if (null === $search->getQueries()) {
141
            $search->addQuery(new MatchAllQuery());
142
        }
143
144 12
        $count = $repository->count($search);
145
146 11
        if (null !== $restHelper->getSortColumn()) {
147 1
            $search->addSort(
148 1
                new FieldSort($this->uncamelize($restHelper->getSortColumn()), $restHelper->getSortOrder())
149
            );
150
        }
151
152 11
        $search->setSize($limit);
153 11
        $search->setFrom(($page - 1) * $limit);
154
155 11
        $result = [];
156 11
        foreach ($repository->execute($search) as $document) {
0 ignored issues
show
Deprecated Code introduced by
The method ONGR\ElasticsearchBundle...e\Repository::execute() has been deprecated with message: Use strict execute functions instead. e.g. executeIterator, executeRawIterator.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
157 9
            if (false !== ($index = array_search($document->getUuid(), $ids))) {
158 1
                $result[$index] = $document;
159
            } else {
160 11
                $result[] = $document;
161
            }
162
        }
163
164 11
        if (count($ids)) {
165 1
            ksort($result);
166 1
            $result = array_values($result);
167
        }
168
169 11
        return $this->handleView(
170 11
            $this->view(
0 ignored issues
show
Documentation introduced by
$this->view(new \Sulu\Co...$page, $limit, $count)) is of type this<Sulu\Bundle\Article...ller\ArticleController>, but the function expects a object<FOS\RestBundle\View\View>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
171 11
                new ListRepresentation(
172
                    $result,
173 11
                    'articles',
174 11
                    'get_articles',
175 11
                    $request->query->all(),
176
                    $page,
177
                    $limit,
178
                    $count
0 ignored issues
show
Bug introduced by
It seems like $count defined by $repository->count($search) on line 144 can also be of type array; however, Sulu\Component\Rest\List...entation::__construct() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
179
                )
180
            )
181
        );
182
    }
183
184
    /**
185
     * Returns single article.
186
     *
187
     * @param string $uuid
188
     * @param Request $request
189
     *
190
     * @return Response
191
     */
192 2
    public function getAction($uuid, Request $request)
193
    {
194 2
        $locale = $this->getRequestParameter($request, 'locale', true);
195 2
        $document = $this->getDocumentManager()->find(
196
            $uuid,
197
            $locale,
198
            [
199 2
                'load_ghost_content' => true,
200
                'load_shadow_content' => false,
201
            ]
202
        );
203
204 2
        return $this->handleView(
205 2
            $this->view($document)->setSerializationContext(
0 ignored issues
show
Bug introduced by
The method setSerializationContext() does not seem to exist on object<Sulu\Bundle\Artic...ller\ArticleController>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
206 2
                SerializationContext::create()->setSerializeNull(true)->setGroups(['defaultPage'])
207
            )
208
        );
209
    }
210
211
    /**
212
     * Create article.
213
     *
214
     * @param Request $request
215
     *
216
     * @return Response
217
     */
218 30
    public function postAction(Request $request)
219
    {
220 30
        $action = $request->get('action');
221 30
        $document = $this->getDocumentManager()->create(self::DOCUMENT_TYPE);
222 30
        $locale = $this->getRequestParameter($request, 'locale', true);
223 30
        $data = $request->request->all();
224
225 30
        $this->persistDocument($data, $document, $locale);
226 30
        $this->handleActionParameter($action, $document, $locale);
227 30
        $this->getDocumentManager()->flush();
228
229 30
        return $this->handleView(
230 30
            $this->view($document)->setSerializationContext(
0 ignored issues
show
Bug introduced by
The method setSerializationContext() does not seem to exist on object<Sulu\Bundle\Artic...ller\ArticleController>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
231 30
                SerializationContext::create()->setSerializeNull(true)->setGroups(['defaultPage'])
232
            )
233
        );
234
    }
235
236
    /**
237
     * Update articles.
238
     *
239
     * @param Request $request
240
     * @param string $uuid
241
     *
242
     * @return Response
243
     */
244 7
    public function putAction(Request $request, $uuid)
245
    {
246 7
        $locale = $this->getRequestParameter($request, 'locale', true);
247 7
        $action = $request->get('action');
248 7
        $data = $request->request->all();
249
250 7
        $document = $this->getDocumentManager()->find(
251
            $uuid,
252
            $locale,
253
            [
254 7
                'load_ghost_content' => false,
255
                'load_shadow_content' => false,
256
            ]
257
        );
258
259 7
        $this->get('sulu_hash.request_hash_checker')->checkHash($request, $document, $document->getUuid());
260
261 7
        $this->persistDocument($data, $document, $locale);
262 7
        $this->handleActionParameter($action, $document, $locale);
263 7
        $this->getDocumentManager()->flush();
264
265 7
        return $this->handleView(
266 7
            $this->view($document)->setSerializationContext(
0 ignored issues
show
Bug introduced by
The method setSerializationContext() does not seem to exist on object<Sulu\Bundle\Artic...ller\ArticleController>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
267 7
                SerializationContext::create()->setSerializeNull(true)->setGroups(['defaultPage'])
268
            )
269
        );
270
    }
271
272
    /**
273
     * Deletes multiple documents.
274
     *
275
     * @param Request $request
276
     *
277
     * @return Response
278
     */
279 1
    public function cdeleteAction(Request $request)
280
    {
281 1
        $ids = array_filter(explode(',', $request->get('ids', '')));
282
283 1
        $documentManager = $this->getDocumentManager();
284 1
        foreach ($ids as $id) {
285 1
            $document = $documentManager->find($id);
286 1
            $documentManager->remove($document);
287 1
            $documentManager->flush();
288
        }
289
290 1
        return $this->handleView($this->view(null));
0 ignored issues
show
Documentation introduced by
$this->view(null) is of type this<Sulu\Bundle\Article...ller\ArticleController>, but the function expects a object<FOS\RestBundle\View\View>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
291
    }
292
293
    /**
294
     * Deletes multiple documents.
295
     *
296
     * @param string $id
297
     *
298
     * @return Response
299
     */
300 1
    public function deleteAction($id)
301
    {
302 1
        $documentManager = $this->getDocumentManager();
303 1
        $document = $documentManager->find($id);
304 1
        $documentManager->remove($document);
305 1
        $documentManager->flush();
306
307 1
        return $this->handleView($this->view(null));
0 ignored issues
show
Documentation introduced by
$this->view(null) is of type this<Sulu\Bundle\Article...ller\ArticleController>, but the function expects a object<FOS\RestBundle\View\View>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
308
    }
309
310
    /**
311
     * Trigger a action for given article specified over get-action parameter.
312
     *
313
     * @Post("/articles/{uuid}")
314
     *
315
     * @param string $uuid
316
     * @param Request $request
317
     *
318
     * @return Response
319
     */
320 1
    public function postTriggerAction($uuid, Request $request)
321
    {
322
        // extract parameter
323 1
        $action = $this->getRequestParameter($request, 'action', true);
324 1
        $locale = $this->getRequestParameter($request, 'locale', true);
325
326
        // prepare vars
327 1
        $view = null;
328 1
        $data = null;
329 1
        $userId = $this->getUser()->getId();
330
331
        try {
332
            switch ($action) {
333 1
                case 'unpublish':
334
                    $document = $this->getDocumentManager()->find($uuid, $locale);
335
                    $this->getDocumentManager()->unpublish($document, $locale);
336
                    $this->getDocumentManager()->flush();
337
338
                    $data = $this->getDocumentManager()->find($uuid, $locale);
339
                    break;
340 1
                case 'remove-draft':
341
                    $data = $this->getDocumentManager()->find($uuid, $locale);
342
                    $this->getDocumentManager()->removeDraft($data, $locale);
343
                    $this->getDocumentManager()->flush();
344
                    break;
345 1
                case 'copy-locale':
346 1
                    $destLocales = $this->getRequestParameter($request, 'dest', true);
347 1
                    $data = $this->getMapper()->copyLanguage($uuid, $userId, null, $locale, explode(',', $destLocales));
348 1
                    break;
349
                default:
350
                    throw new RestException('Unrecognized action: ' . $action);
351
            }
352
353
            // prepare view
354 1
            $view = $this->view($data, $data !== null ? 200 : 204);
355 1
            $view->setSerializationContext(SerializationContext::create()->setGroups(['defaultPage']));
0 ignored issues
show
Bug introduced by
The method setSerializationContext() does not seem to exist on object<Sulu\Bundle\Artic...ller\ArticleController>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
356
        } catch (RestException $exc) {
357
            $view = $this->view($exc->toArray(), 400);
358
        }
359
360 1
        return $this->handleView($view);
0 ignored issues
show
Documentation introduced by
$view is of type this<Sulu\Bundle\Article...ller\ArticleController>, but the function expects a object<FOS\RestBundle\View\View>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
361
    }
362
363
    /**
364
     * {@inheritdoc}
365
     */
366 31
    public function getSecurityContext()
367
    {
368 31
        return ArticleAdmin::SECURITY_CONTEXT;
369
    }
370
371
    /**
372
     * Persists the document using the given information.
373
     *
374
     * @param array $data
375
     * @param object $document
376
     * @param string $locale
377
     *
378
     * @throws InvalidFormException
379
     * @throws MissingParameterException
380
     */
381 30
    private function persistDocument($data, $document, $locale)
382
    {
383 30
        $form = $this->createForm(
384 30
            ArticleDocumentType::class,
385
            $document,
386
            [
387
                // disable csrf protection, since we can't produce a token, because the form is cached on the client
388 30
                'csrf_protection' => false,
389
            ]
390
        );
391 30
        $form->submit($data, false);
392
393 30
        if (!$form->isValid()) {
394
            throw new InvalidFormException($form);
395
        }
396
397 30
        $this->getDocumentManager()->persist(
398
            $document,
399
            $locale,
400
            [
401 30
                'user' => $this->getUser()->getId(),
402
                'clear_missing_content' => false,
403 30
                'route_path' => array_key_exists('routePath', $data) ? $data['routePath'] : null,
404
            ]
405
        );
406 30
    }
407
408
    /**
409
     * Returns document-manager.
410
     *
411
     * @return DocumentManagerInterface
412
     */
413 30
    protected function getDocumentManager()
414
    {
415 30
        return $this->get('sulu_document_manager.document_manager');
416
    }
417
418
    /**
419
     * @return ContentMapperInterface
420
     */
421 1
    protected function getMapper()
422
    {
423 1
        return $this->get('sulu.content.mapper');
424
    }
425
426
    /**
427
     * Delegates actions by given actionParameter, which can be retrieved from the request.
428
     *
429
     * @param string $actionParameter
430
     * @param object $document
431
     * @param string $locale
432
     */
433 30
    private function handleActionParameter($actionParameter, $document, $locale)
434
    {
435
        switch ($actionParameter) {
436 30
            case 'publish':
437 10
                $this->getDocumentManager()->publish($document, $locale);
438 10
                break;
439
        }
440 30
    }
441
442
    /**
443
     * Converts camel case string into normalized string with underscore.
444
     *
445
     * @param string $camel
446
     *
447
     * @return string
448
     */
449 1
    private function uncamelize($camel)
450
    {
451 1
        $camel = preg_replace(
452 1
            '/(?!^)[[:upper:]][[:lower:]]/',
453 1
            '$0',
454 1
            preg_replace('/(?!^)[[:upper:]]+/', '_$0', $camel)
455
        );
456
457 1
        return strtolower($camel);
458
    }
459
}
460